From 8b08db88f7d77c511f80bd3411edb754cb29c1ab Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 25 Sep 2025 13:50:00 -0400 Subject: [PATCH 01/81] i don't know why my editor keeps saving this at utf-16 Signed-off-by: John Parent From 37bab634c45a8602e141aea2f82cf57640b88346 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 19 Jun 2025 17:21:07 -0400 Subject: [PATCH 02/81] Spack Package side MSVC compiler wrapper support Updates the compiler wrapper package to handle Windows compiler wrappers Adds builders for the new compiler wrapper flavor Adds proper env setup for the wrapper Updates the MSVC package for compiler wrapper support --- .../packages/compiler_wrapper/fixup11.patch | 1237 +++++++++++++++++ .../packages/compiler_wrapper/package.py | 297 ++-- .../builtin/packages/msvc/package.py | 40 +- 3 files changed, 1432 insertions(+), 142 deletions(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch new file mode 100644 index 00000000000..cd1e84b7780 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch @@ -0,0 +1,1237 @@ +diff --git a/Makefile b/Makefile +index 51fe35d..3480939 100644 +--- a/Makefile ++++ b/Makefile +@@ -22,7 +22,7 @@ PREFIX="$(MAKEDIR)\install\" + !ENDIF + + !IF "$(BUILD_TYPE)" == "DEBUG" +-BUILD_CFLAGS = /Zi ++BUILD_CFLAGS = /Zi /fsanitize=address + BUILD_LINK = /DEBUG + !ENDIF + +@@ -58,8 +58,8 @@ setup_test: cl.exe + + build_and_check_test_sample : setup_test + cd tmp\test +- cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS +- cl /c /EHsc ..\..\test\main.cxx ++ cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS /I ..\..\test\include ++ cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include + link $(LFLAGS) calc.obj /out:calc.dll /DLL + link $(LFLAGS) main.obj calc.lib /out:tester.exe + tester.exe +@@ -77,15 +77,34 @@ test_wrapper : build_and_check_test_sample + rmdir /q /s tmp_bin + cd .. + +-test_relocate: build_and_check_test_sample ++test_relocate_exe: build_and_check_test_sample + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe tester.exe --deploy --full +- relocate.exe tester.exe --export --full ++ relocate.exe --pe tester.exe --deploy --full ++ relocate.exe --pe tester.exe --export --full + tester.exe ++ move ..\calc.dll calc.dll ++ cd ../.. + +-test: test_wrapper test_relocate ++test_relocate_dll: build_and_check_test_sample ++ cd tmp/test ++ -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe ++ cd .. ++ mkdir tmp_bin ++ mkdir tmp_lib ++ move test\calc.dll tmp_bin\calc.dll ++ move test\calc.lib tmp_lib\calc.lib ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ cd test ++ del tester.exe ++ link main.obj ..\tmp_lib\calc.lib /out:tester.exe ++ .\tester.exe ++ ++test_and_cleanup: test clean-test ++ ++ ++test: test_wrapper test_relocate_exe test_relocate_dll + + + clean : clean-test clean-cl +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 770d786..9f8aaa0 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -37,7 +37,7 @@ int redefinedArgCheck(const std::map &args, const char + int checkArgumentPresence(const std::map &args, const char * val, bool required = true) + { + if (args.find(val) == args.end()) { +- std::cerr << "Warning! Argument (" << val << ") not present"; ++ std::cerr << "Warning! Argument (" << val << ") not present\n"; + if (required) { + return 0; + } +@@ -58,19 +58,12 @@ std::map ParseRelocate(const char ** args, int argc) { + } + opts.insert(std::pair("pe", args[++i])); + } +- else if (endswith((std::string)args[i], ".dll")) { +- if(redefinedArgCheck(opts, "pe", "pe")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("pe", args[i])); +- } +- else if (endswith((std::string)args[i], ".exe")) { +- if(redefinedArgCheck(opts, "pe", "pe")) { ++ else if (!strcmp(args[i], "--coff")) { ++ if(redefinedArgCheck(opts, "coff", "--coff")) { + opts.clear(); + return opts; + } +- opts.insert(std::pair("pe", args[i])); ++ opts.insert(std::pair("coff", args[++i])); + } + else if (!strcmp(args[i], "--full")) { + if(redefinedArgCheck(opts, "full", "--full")) { +@@ -99,15 +92,20 @@ std::map ParseRelocate(const char ** args, int argc) { + } + opts.insert(std::pair("cmd", "deploy")); + } ++ else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { ++ opts.insert(std::pair("debug", "on")); ++ } ++ else if (!strcmp(args[i], "--verify")) { ++ opts.insert(std::pair("verify", "on")); ++ } ++ else if (!strcmp(args[i], "--report")) { ++ opts.insert(std::pair("report", "report")); ++ } + else { + // Unknown argument, warn the user it will not be used +- std::cerr << "Unknown argument :" << args[i] << "will be ignored\n"; ++ std::cerr << "Unknown argument: " << args[i] << " will be ignored\n"; + } + } +- if(!checkArgumentPresence(opts, "pe")) { +- opts.clear(); +- return opts; +- } + return opts; + } + +@@ -165,29 +163,37 @@ bool print_help() + std::cout << " In this case, the CLI of this wrapper is identical to cl|ifx|link.\n"; + std::cout << " See https://learn.microsoft.com/en-us/cpp/build/reference/c-cpp-building-reference\n"; + std::cout << "\n"; +- std::cout << " cl.exe /c foo.c"; ++ std::cout << " cl.exe /c foo.c\n"; + std::cout << "\n"; + std::cout << " To preform relocation, invoke the 'relocate' symlink to this file:\n"; + std::cout << "\n"; + std::cout << " Options:\n"; +- std::cout << " [--pe] = PE file to be relocated\n"; ++ std::cout << " --pe = PE (dll/exe) file to be relocated\n"; ++ std::cout << " [--coff ] = COFF (import library) file to be relocated\n"; ++ std::cout << " If relocating an exe, this is not required.\n"; + std::cout << " --full = Relocate dynamic references inside\n"; +- std::cout << " the dll in addition to re-generating\n"; ++ std::cout << " the pe in addition to re-generating\n"; + std::cout << " the import library\n"; + std::cout << " Note: this is assumed to be true if\n"; + std::cout << " relocating an executable.\n"; + std::cout << " If an executable is relocated, no import\n"; + std::cout << " library operations are performed.\n"; ++ std::cout << " When relocating a DLL, the import library for\n"; ++ std::cout << " said library is regenerated and the old imp lib\n"; ++ std::cout << " replaced.\n"; + std::cout << " --export|--deploy = Mutually exclusive command modifier.\n"; + std::cout << " Instructs relocate to either prepare the\n"; + std::cout << " dynamic library for exporting to build cache\n"; + std::cout << " or for extraction from bc onto new host system\n"; + std::cout << " --report = Report information about the parsed PE/Coff files\n"; ++ std::cout << " --debug|-d = Debug relocate run\n"; ++ std::cout << " --verify = Validates that the given file 'pe' is an import library\n"; + std::cout << "\n"; + std::cout << " To report on PE/COFF files, invoke the 'reporter' symlink to this executable or use the --report flag when invoking 'relocate'"; + std::cout << "\n"; + std::cout << " Options:\n"; + std::cout << " = Path to any PE or COFF file\n"; ++ std::cout << " To debug any flavor of this wrapper, set the environment variable SPACK_DEBUG_WRAPPER to any value in the wrapper context.\n"; + std::cout << "\n"; + return true; + } +@@ -204,4 +210,3 @@ bool CheckAndPrintHelp(const char ** arg, int argc) + return false; + + } +- +diff --git a/src/execute.cxx b/src/execute.cxx +index b89fff3..77d49a6 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -72,6 +72,7 @@ void ExecuteCommand::SetupExecute() + */ + int ExecuteCommand::CreateChildPipes() + { ++ // Create stdout pipes + SECURITY_ATTRIBUTES saAttr; + // Set the bInheritHandle flag so pipe handles are inherited. + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); +@@ -80,8 +81,21 @@ int ExecuteCommand::CreateChildPipes() + this->saAttr = saAttr; + if( !CreatePipe(&this->ChildStdOut_Rd, &this->ChildStdOut_Wd, &saAttr, 0) ) + return 0; +- if ( !SetHandleInformation(ChildStdOut_Rd, HANDLE_FLAG_INHERIT, 0) ) ++ if ( !SetHandleInformation(this->ChildStdOut_Rd, HANDLE_FLAG_INHERIT, 0) ) + return 0; ++ ++ // create stderr pipes ++ SECURITY_ATTRIBUTES saAttrErr; ++ // Set the bInheritHandle flag so pipe handles are inherited. ++ saAttrErr.nLength = sizeof(SECURITY_ATTRIBUTES); ++ saAttrErr.bInheritHandle = TRUE; ++ saAttrErr.lpSecurityDescriptor = NULL; ++ this->saAttrErr = saAttrErr; ++ if( !CreatePipe(&this->ChildStdErr_Rd, &this->ChildStdErr_Wd, &saAttrErr, 0) ) ++ return 0; ++ if ( !SetHandleInformation(this->ChildStdErr_Rd, HANDLE_FLAG_INHERIT, 0) ) ++ return 0; ++ + return 1; + } + +@@ -92,6 +106,7 @@ int ExecuteCommand::CreateChildPipes() + bool ExecuteCommand::ExecuteToolChainChild() + { + LPVOID lpMsgBuf; ++ debug("Executing Command: " + this->ComposeCLI()); + const std::wstring c_commandLine = ConvertAnsiToWide(this->ComposeCLI()); + wchar_t * nc_commandLine = _wcsdup(c_commandLine.c_str()); + if(! CreateProcessW( +@@ -118,6 +133,8 @@ bool ExecuteCommand::ExecuteToolChainChild() + (LPTSTR) &lpMsgBuf, + 0, NULL + ); ++ std::cerr << "Failed to initiate child process from: " << ConvertWideToANSI(nc_commandLine) << " "; ++ std::cerr << "With error: "; + std::cerr << (char*)lpMsgBuf << "\n"; + free(nc_commandLine); + this->cpw_initalization_failure = true; +@@ -132,6 +149,38 @@ bool ExecuteCommand::ExecuteToolChainChild() + return true; + } + ++ ++/* ++ * Reads for the member variable holding a pipe to the wrapped processes' ++ * STDERR and writes either to this processes' STDERR or a file, depending on ++ * how the process wrapper is configured ++ */ ++int ExecuteCommand::PipeChildToStdErr() ++{ ++ DWORD dwRead, dwWritten; ++ CHAR chBuf[BUFSIZE]; ++ BOOL bSuccess = TRUE; ++ HANDLE hParentOut; ++ if (this->write_to_file) { ++ hParentOut = this->fileout; ++ } ++ else { ++ hParentOut = GetStdHandle(STD_ERROR_HANDLE); ++ } ++ ++ for (;;) ++ { ++ bSuccess = ReadFile( this->ChildStdErr_Rd, chBuf, BUFSIZE, &dwRead, NULL); ++ if( ! bSuccess || dwRead == 0 ) break; ++ ++ bSuccess = WriteFile(hParentOut, chBuf, ++ dwRead, &dwWritten, NULL); ++ if (! bSuccess ) break; ++ } ++ return !bSuccess; ++} ++ ++ + /* + * Reads for the member variable holding a pipe to the wrapped processes' + * STDOUT and writes either to this processes' STDOUT or a file, depending on +@@ -230,9 +279,10 @@ bool ExecuteCommand::Execute(const std::string &filename) + NULL + ); + } +- int ret_code = this->ExecuteToolChainChild(); ++ bool ret_code = this->ExecuteToolChainChild(); + if (ret_code) { + this->child_out_future = std::async(std::launch::async, &ExecuteCommand::PipeChildToStdout, this); ++ this->child_err_future = std::async(std::launch::async, &ExecuteCommand::PipeChildToStdErr, this); + this->exit_code_future = std::async(std::launch::async, &ExecuteCommand::ReportExitCode, this); + } + return ret_code; +@@ -246,5 +296,7 @@ int ExecuteCommand::Join() + { + if(!this->child_out_future.get()) + return -999; ++ if(!this->child_err_future.get()) ++ return -999; + return this->exit_code_future.get(); + } +diff --git a/src/execute.h b/src/execute.h +index 8449517..94c5a74 100644 +--- a/src/execute.h ++++ b/src/execute.h +@@ -37,6 +37,7 @@ private: + void SetupExecute(); + bool ExecuteToolChainChild(); + int PipeChildToStdout(); ++ int PipeChildToStdErr(); + int CreateChildPipes(); + int CleanupHandles(); + int ReportExitCode(); +@@ -44,15 +45,21 @@ private: + // pipe from child process stdout + // to parent std out or file + std::future child_out_future; ++ // Holds the exit code of the pipe ++ // from child to parent stderr ++ std::future child_err_future; + // Holds the exit code of the + // command wrapped by this class + std::future exit_code_future; + std::string ComposeCLI(); + HANDLE ChildStdOut_Rd; + HANDLE ChildStdOut_Wd; ++ HANDLE ChildStdErr_Rd; ++ HANDLE ChildStdErr_Wd; + PROCESS_INFORMATION procInfo; + STARTUPINFOW startInfo; + SECURITY_ATTRIBUTES saAttr; ++ SECURITY_ATTRIBUTES saAttrErr; + HANDLE fileout = INVALID_HANDLE_VALUE; + bool write_to_file; + bool cpw_initalization_failure = false; +diff --git a/src/ld.cxx b/src/ld.cxx +index c4eb315..7cbe9fb 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -33,13 +33,16 @@ int LdInvocation::InvokeToolchain() + // We're creating a dll, we need to create an appropriate import lib + if(!link_run.IsExeLink()) { + std::string basename = link_run.get_name(); +- std::string imp_lib_name = strip(basename, ".dll") + ".lib"; ++ std::string imp_lib_name = link_run.get_implib_name(); + std::string dll_name = link_run.get_mangled_out(); +- std::string abs_out_imp_lib_name = basename + ".dll-abs.lib"; ++ std::string abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; ++ std::string def_file = link_run.get_def_file(); ++ std::string def_line = "-def"; ++ def_line += !def_file.empty() ? ":" + def_file : ""; + // create command line to generate new import lib + this->rpath_executor = ExecuteCommand("lib.exe", this->ComposeCommandLists( + { +- {"-def", "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, ++ {def_line, "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, + this->obj_args, + this->lib_args, + this->lib_dir_args, +@@ -52,13 +55,25 @@ int LdInvocation::InvokeToolchain() + } + CoffReaderWriter cr(abs_out_imp_lib_name); + CoffParser coff(&cr); +- coff.Parse(); ++ if(!coff.Parse()) { ++ debug("Failed to parse COFF file: " + abs_out_imp_lib_name); ++ return -9; ++ } + if(!coff.NormalizeName(dll_name)){ ++ debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); + return -9; + } +- std::remove(imp_lib_name.c_str()); +- std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); +- ++ debug("Renaming library from " + abs_out_imp_lib_name + " to " + imp_lib_name); ++ int remove_exitcode = std::remove(imp_lib_name.c_str()); ++ if(remove_exitcode) { ++ debug("Failed to remove original import library with exit code: " + remove_exitcode); ++ return -10; ++ } ++ int rename_exitcode = std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); ++ if(rename_exitcode) { ++ debug("Failed to rename temporary import library with exit code: " + rename_exitcode); ++ return -11; ++ } + } + return ret_code; + } +diff --git a/src/main.cxx b/src/main.cxx +index a1a6617..829847f 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -25,16 +25,61 @@ int main(int argc, const char* argv[]) { + bool deploy = !(patch_args.find("cmd") == patch_args.end()) + && patch_args.at("cmd") == "deploy"; + bool report = !(patch_args.find("report") == patch_args.end()); +- bool is_exe = endswith(patch_args.at("pe"), ".exe"); ++ bool has_pe = !(patch_args.find("pe") == patch_args.end()); ++ bool is_exe = has_pe ? endswith(patch_args.at("pe"), ".exe") : false; ++ bool debug = !(patch_args.find("debug") == patch_args.end()); ++ bool is_validate = !(patch_args.find("verify") == patch_args.end()); ++ bool has_coff = !(patch_args.find("coff") == patch_args.end()); + // Without full with a DLL we re-produce the import lib from the + // relocated DLL, but with an EXE there is nothing to do ++ if(!has_coff && !has_pe) { ++ std::cout << "No binaries to operate on... nothing to do\n"; ++ return -1; ++ } + if (is_exe && !full) + { +- std::cerr << "Executable file provided but --full not specified, nothing to do...\n"; ++ std::cout << "Executable file provided but --full not specified, nothing to do...\n"; ++ return -1; ++ } ++ // The only scenario its ok to have a dll/exe and no coff is when we're creating a cache ++ // entry ++ if (!is_exe && !has_coff && !deploy) { ++ std::cout << "Attempting to relocate DLL without coff file, please provide a coff file.\n"; ++ return -1; ++ } ++ if (is_validate && !has_coff) { ++ std::cout << "Attempting to validate without a coff file, nothing to validate\n"; ++ return -1; ++ } ++ if (report && !has_coff) { ++ std::cout << "Attempting to report without a binary, nothing to report...\n"; ++ return -1; ++ } ++ if (!(is_validate || report) && !has_pe) { ++ std:: cout << "Attempting to perform relocation without a PE file, please provide one.\n"; ++ return -1; ++ } ++ if (is_validate) { ++ return CoffParser::Validate(patch_args.at("coff")); ++ } ++ if (report) { ++ CoffReaderWriter cr(patch_args.at("coff")); ++ CoffParser coff(&cr); ++ if(!reportCoff(coff)) { ++ return 1; ++ } + return 0; + } +- LibRename rpath_lib(patch_args.at("pe"), full, deploy, true, report); +- if(!rpath_lib.ExecuteRename()){ ++ DEBUG = debug; ++ std::unique_ptr rpath_lib; ++ if (has_coff) ++ { ++ rpath_lib = std::make_unique(patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); ++ } ++ else { ++ rpath_lib = std::make_unique(patch_args.at("pe"), full, deploy, true); ++ } ++ if(!rpath_lib->ExecuteRename()){ + std::cerr << "Library rename failed\n"; + return 9; + } +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index b71a167..b4fb3a8 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -6,6 +6,8 @@ + #include "toolchain.h" + + #include ++#include ++ + + ToolChainInvocation::ToolChainInvocation(std::string command, char const* const* cli) : + command(command) +@@ -42,10 +44,12 @@ int ToolChainInvocation::InvokeToolchain() { + this->executor = ExecuteCommand( this->command, + commandLine + ); ++ debug("Setting up executor for " + std::string(typeid(*this).name()) + "toolchain"); ++ debug("Toolchain: " + this->command); + // Run first pass of command as requested by caller + int ret_code = this->executor.Execute(); + if(!ret_code) { +- std::cerr << "Unable to launch process \n"; ++ std::cerr << "Unable to launch toolchain process \n"; + return -9999; + } + return this->executor.Join(); +@@ -56,7 +60,7 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // Includes come first + for( char const* const* c = cli; *c; c++ ){ + std::string arg = std::string(*c); +- if ( startswith(arg, "/I") ) { ++ if ( startswith(arg, "/I") || startswith(arg, "-I") ) { + // We have an include arg + // can have an optional space + // check if there are characters after +@@ -64,10 +68,12 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // argument to be the include + if (arg.size() > 2) + this->include_args.push_back(arg); +- else ++ else { ++ this->include_args.push_back(arg); + this->include_args.push_back(std::string(*(++c))); ++ } + } +- else if( endswith(arg, ".lib") ) ++ else if( endswith(arg, ".lib") && (arg.find("implib:") == std::string::npos)) + // Lib args are just libraries + // provided like system32.lib on the + // command line. +@@ -102,6 +108,8 @@ StrList ToolChainInvocation::ComposeCommandLists(std::vector command_ar + StrList commandLine; + for(auto arg_list : command_args) + { ++ // Ensure arguments are appropriately quoted ++ quoteList(arg_list); + commandLine.insert(commandLine.end(), arg_list.begin(), arg_list.end()); + } + return commandLine; +diff --git a/src/utils.cxx b/src/utils.cxx +index d96876e..3300102 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -162,8 +162,7 @@ void StripPathAndExe(std::string &command) { + + void StripExe(std::string &command) { + // Normalize command to lowercase to avoid parsing issues +- std::transform(command.begin(), command.end(), command.begin(), +- [](unsigned char c){ return std::tolower(c); }); ++ lower(command); + std::string::size_type loc = command.rfind(".exe"); + if ( std::string::npos != loc && loc + 4 == command.length() ) + command.erase(loc); +@@ -173,6 +172,29 @@ void StripPath(std::string &command) { + command.erase(0, command.find_last_of("\\/") + 1); + } + ++/** ++ * Converts a string to lowercase ++ * ++ * \arg str - string to be made lowercase ++ */ ++void lower(std::string &str) { ++ std::transform(str.begin(), str.end(), str.begin(), ++ [](unsigned char c){ return std::tolower(c); }); ++} ++ ++ ++std::string quoteAsNeeded(std::string &str) { ++ if (str.find_first_of(" &<>|()") != std::string::npos) { ++ // There are spaces or special characters in string, quote it ++ return "\"" + str + "\""; ++ } ++ return str; ++} ++ ++ ++void quoteList(StrList &args) { ++ std::transform(args.begin(), args.end(), args.begin(), quoteAsNeeded); ++} + + /** + * Given an environment variable name +@@ -235,7 +257,7 @@ std::string basename(const std::string &file) + { + std:size_t last_path = file.find_last_of("\\")+1; + if (last_path == std::string::npos) { +- return std::string(); ++ return file; + } + return file.substr(last_path); + } +@@ -276,6 +298,16 @@ DWORD RvaToFileOffset(PIMAGE_SECTION_HEADER §ion_header, DWORD number_of_sec + } + + ++void debug(std::string dbgStmt) { ++ if (DEBUG || getenv("SPACK_DEBUG_WRAPPER")) { ++ std::cout << "DEBUG: " << dbgStmt << "\n"; ++ } ++} ++ ++void debug(char * dbgStmt, int len) { ++ debug(std::string(dbgStmt, len)); ++} ++ + std::string reportLastError() + { + DWORD error = GetLastError(); +@@ -412,6 +444,19 @@ DWORD ToLittleEndian(DWORD val) + return little_endian_val; + } + ++int get_slash_name_length(char *slash_name) ++{ ++ if(slash_name == nullptr) { ++ return 0; ++ } ++ int len = 0; ++ // Maximum length for a given name in the PE/COFF format is 143 chars ++ while(slash_name[len] != '/' && len < 144) { ++ ++len; ++ } ++ return len; ++} ++ + char * findstr(char *search_str, const char * substr, int size) + { + char * search = search_str; +diff --git a/src/utils.h b/src/utils.h +index 6dc2222..a5376bd 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -59,6 +59,9 @@ StrList split(const std::string &s, const std::string &delim); + //Strips substr off the RHS of the larger string + std::string strip(const std::string& s, const std::string &substr); + ++//Strips substr of LHS of the larger string ++std::string lstrip(const std::string& s, const std::string &substr); ++ + // Joins vector of strings by join character + std::string join(const StrList &strs, const std::string &join_char = " "); + +@@ -78,10 +81,22 @@ void StripExe(std::string &command); + // resulting in a parentless, non exe extensioned path + void StripPathAndExe(std::string &command); + ++// Make str lowercase ++void lower(std::string &str); ++ ++// Given a string containing something terminated by a ++// forward slash, get the length of the substr terminated ++// by / ++int get_slash_name_length(char *slash_name); ++ + // Implementation of strstr but serch is bounded at size and + // does not terminate on the first read nullptr + char * findstr(char * search_str, const char * substr, int size); + ++// Adds quote to relevent strings in a list of strings ++// Strings to be quoted contain: spaces, or any of &<>|() ++void quoteList(StrList &args); ++ + // FS/Path helpers // + + // Returns current working directory +@@ -109,6 +124,12 @@ std::string reportLastError(); + // files in big endian format + DWORD ToLittleEndian(DWORD val); + ++// Operating Utils // ++ ++void debug(std::string dbgStmt); ++ ++void debug(char * dbgStmt, int len); ++ + /** + * Library Searching utility class + * Collection of heuristics and logic surrounding library +@@ -132,3 +153,5 @@ public: + std::string FindLibrary(const std::string &lib_name, const std::string &lib_path); + void EvalSearchPaths(); + }; ++ ++static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index 6eb7863..b2cad80 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -160,24 +160,45 @@ LinkerInvocation::LinkerInvocation(const StrList &linkLine) + void LinkerInvocation::Parse() + { + for (auto token = this->tokens.begin(); token != this->tokens.end(); ++token) { +- if (endswith(*token, ".lib")) { ++ std::string normalToken = *token; ++ lower(normalToken); ++ // implib specifies the eventuall import libraries name ++ // and thus will contain a ".lib" extension, which ++ // the next check will process as a library argument ++ if (normalToken.find("implib:") != std::string::npos) { ++ // If there was nothing after the ":", the ++ // previous link command would have failed ++ // and : is not a legal character in a name ++ // guarantees this split command produces a vec of ++ // len 2 ++ StrList implibLine = split(*token, ":"); ++ this->implibname = implibLine[1]; ++ } ++ else if (endswith(normalToken, ".lib")) { + this->libs.push_back(*token); + } +- else if (*token == "/dll" || *token == "/DLL") { ++ else if (normalToken == "/dll" || normalToken == "-dll") { + this->is_exe = false; + } +- else if (startswith(*token, "-out") || startswith(*token, "/out")) { ++ else if (startswith(normalToken, "-out") || startswith(normalToken, "/out")) { + this->output = split(*token, ":")[1]; + } +- else if (endswith(*token, ".obj")) { ++ else if (endswith(normalToken, ".obj")) { + this->objs.push_back(*token); + } ++ else if (normalToken.find("def:") != std::string::npos) { ++ StrList defLine = split(*token, ":"); ++ this->def_file = defLine[1]; ++ } + } + std::string ext = this->is_exe ? ".exe" : ".dll"; + if (this->output.empty()){ + this->output = strip(this->objs.front(), ".obj") + ext; + } + this->name = strip(this->output, ext); ++ if (this->implibname.empty()) { ++ this->implibname = this->name + ".lib"; ++ } + } + + std::string LinkerInvocation::get_name() +@@ -185,6 +206,16 @@ std::string LinkerInvocation::get_name() + return this->name; + } + ++std::string LinkerInvocation::get_implib_name() ++{ ++ return this->implibname; ++} ++ ++std::string LinkerInvocation::get_def_file() ++{ ++ return this->def_file; ++} ++ + std::string LinkerInvocation::get_out() + { + return this->output; +@@ -330,7 +361,10 @@ bool CoffParser::Parse() + std::streampos offset = this->coffStream->tell(); + this->coffStream->ReadHeader(header); + this->coffStream->ReadMember(header, member); +- this->ParseData(header, member); ++ if (!this->ParseData(header, member)) { ++ this->verified = true; ++ return false; ++ } + coff_entry entry; + entry.header = header; + entry.member = member; +@@ -347,6 +381,22 @@ bool CoffParser::Parse() + return true; + } + ++int CoffParser::Verify() ++{ ++ bool parseStatus = this->Parse(); ++ if(!parseStatus && !this->verified) { ++ // actual error in parsing the library ++ return 2; ++ } ++ else if(!parseStatus && this->verified) { ++ // library is valid, it's just a static ++ // lib, not an import ++ return 1; ++ } ++ // otherwise, successful, it's an import lib ++ return 0; ++} ++ + /** + * Parses a member section in the form of a short format import section + * based on the COFF structure scheme +@@ -468,6 +518,17 @@ void CoffParser::ParseSecondLinkerMember(coff_member *member) + member->second_link = sl; + } + ++ ++namespace { ++ bool nameCheck(BYTE* name) ++ { ++ int nameLen = get_slash_name_length((char*)name); ++ if(findstr((char*)name, ".obj", nameLen)) { ++ return false; ++ } ++ return true; ++ } ++} + /** + * Drive the parsing of the "data" section of an import library member + * +@@ -482,7 +543,7 @@ void CoffParser::ParseSecondLinkerMember(coff_member *member) + * \param header A pointer to the archive member header corresponding to the member being parsed + * \param member A pointer to the member data being parsed + */ +-void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member) ++bool CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member) + { + IMPORT_OBJECT_HEADER * p_imp_header = (IMPORT_OBJECT_HEADER *)member->data; + if((p_imp_header->Sig1 == IMAGE_FILE_MACHINE_UNKNOWN) && (p_imp_header->Sig2 == IMPORT_OBJECT_HDR_SIG2)) { +@@ -490,6 +551,9 @@ void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *mem + this->ParseShortImport(member); + } + else if (!strncmp((char*)header->Name, IMAGE_ARCHIVE_LINKER_MEMBER, 16)) { ++ if(!nameCheck(header->Name)){ ++ return false; ++ } + if (!this->coff.read_first_linker) { + this->ParseFirstLinkerMember(member); + this->coff.read_first_linker = true; +@@ -499,13 +563,36 @@ void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *mem + } + } + else if (!strncmp((char*)header->Name, IMAGE_ARCHIVE_LONGNAMES_MEMBER, 16)) { +- // Long names member doesn't provide us anything useful to parse +- // at this stage +- return; ++ // Check the long names member for values, if so, check the extension has a dll ++ if (!this->ValidateLongName(member, atoi((char*)header->Size))) { ++ return false; ++ } ++ member->is_longname = true; + } + else { ++ if(!nameCheck(header->Name)) { ++ return false; ++ } + this->ParseFullImport(member); + } ++ return true; ++} ++ ++bool CoffParser::ValidateLongName(coff_member* member, int size) ++{ ++ if (!member->data) { ++ // If we have no member, by virtue of correctly processing ++ // the header to get to this point ++ // we have a valid header ++ return true; ++ } ++ // If a name has an object file, this is not an import ++ // member ++ char * objRes = findstr(member->data, ".obj", size); ++ if (!objRes) { ++ return true; ++ } ++ return false; + } + + +@@ -553,11 +640,7 @@ void CoffParser::NormalizeSectionNames(const std::string &name, char* section, c + char * new_name = new char[name_len]; + strncpy(new_name, section_search_start, name_len); + replace_special_characters(new_name, name_len); +- this->coffStream->seek(0); +- this->coffStream->seek(section_data_start_offset + offset); +- // Reduce name len by one to prevent writing out the null terminator +- // that is part of the new_name +- this->coffStream->write(new_name, name_len); ++ this->writeRename(new_name, name_len, section_data_start_offset + offset); + delete new_name; + section_search_start += name_len+1; + offset = section_search_start - section; +@@ -565,6 +648,18 @@ void CoffParser::NormalizeSectionNames(const std::string &name, char* section, c + } + } + ++void CoffParser::writeRename(char* name, const int size, const int loc) ++{ ++ this->coffStream->seek(0); ++ this->coffStream->seek(loc); ++ this->coffStream->write(name, size); ++} ++ ++bool CoffParser::validateName(char* old_name, std::string new_name) ++{ ++ return !strcmp(old_name, new_name.c_str()); ++} ++ + /** + * Normalizes mangled DLL names that represent absolute paths in COFF + * binary files +@@ -609,18 +704,12 @@ bool CoffParser::NormalizeName(std::string &name) + // We know it exists at this point due to the success of the conditional above + char* long_name = new char[long_name_len+1]; + strncpy(long_name, this->coff.members[2].member->data+longname_offset, long_name_len+1); +- // Ensure Dll name is the one we're looking to perform the rename for +- if (!strcmp(name.c_str(), long_name) && !long_name_renamed) { ++ if (this->validateName(long_name, name) && !long_name_renamed) { + // If so, unmangle it + replace_special_characters(long_name, long_name_len+1); + // offset of actual longname member + int offset = std::streamoff(this->coff.members[2].offset); +- this->coffStream->seek(0); +- // Seek to longname header +- this->coffStream->seek(offset); +- // Seek to offset within longname member for a given import name +- this->coffStream->seek(sizeof(IMAGE_ARCHIVE_MEMBER_HEADER) + longname_offset, std::ios_base::cur); +- this->coffStream->write(long_name, long_name_len+1); ++ this->writeRename(long_name, long_name_len+1, offset + sizeof(IMAGE_ARCHIVE_MEMBER_HEADER) + longname_offset); + long_name_renamed = true; + } + delete long_name; +@@ -636,7 +725,7 @@ bool CoffParser::NormalizeName(std::string &name) + strcpy(new_name, mem.member->short_member->short_dll); + replace_special_characters(new_name, name_len); + // ensure it's the name we're looking to rename +- if(!strcmp(name.c_str(), mem.member->short_member->short_dll)) { ++ if(this->validateName(mem.member->short_member->short_dll, name)) { + // Member offset in file + int offset = std::streamoff(mem.offset); + // Member header offset +@@ -647,9 +736,7 @@ bool CoffParser::NormalizeName(std::string &name) + // Next is the symbol name, which is a null terminated string + // +1 to preserve the null terminator in the coff member + offset += strlen(mem.member->short_member->short_name) + 1; +- this->coffStream->seek(0); +- this->coffStream->seek(offset); +- this->coffStream->write(new_name, strlen(new_name)); ++ this->writeRename(new_name, strlen(new_name), offset); + } + delete new_name; + } +@@ -689,9 +776,7 @@ bool CoffParser::NormalizeName(std::string &name) + char * new_no_ext_name = new char[name_len]; + strncpy(new_no_ext_name, string_table_start, name_len); + replace_special_characters(new_no_ext_name, name_len); +- this->coffStream->seek(0); +- this->coffStream->seek(relative_string_table_start_offset + offset); +- this->coffStream->write(new_no_ext_name, name_len); ++ this->writeRename(new_no_ext_name, name_len, relative_string_table_start_offset + offset); + delete new_no_ext_name; + } + } +@@ -750,19 +835,27 @@ void CoffParser::ReportShortImportMember(short_import_member *si) + } + + ++void CoffParser::ReportLongName(char * data) ++{ ++ std::cout << "DLL: " << data << "\n"; ++} ++ + void CoffParser::Report() + { + for (auto mem: this->coff.members) { +- reportArchiveHeader(mem.header); +- if(mem.member->long_member) { +- this->ReportLongImportMember(mem.member->long_member); +- } +- else if(mem.member->short_member) { +- this->ReportShortImportMember(mem.member->short_member); ++ if(mem.member->is_longname) { ++ this->ReportLongName(mem.member->data); + } + } + } + ++int CoffParser::Validate(std::string &coff) ++{ ++ CoffReaderWriter cr(coff); ++ CoffParser coffp(&cr); ++ return coffp.Verify(); ++} ++ + /** + * Reports information about parsed coff file + * +@@ -978,12 +1071,17 @@ bool LibRename::FindDllAndRename(HANDLE &pe_in) + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace, bool report) ++LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace) + : replace(replace), full(full), pe(pe), deploy(deploy) ++{} ++ ++LibRename::LibRename(std::string pe, std::string coff, bool full, bool deploy, bool replace) ++: replace(replace), full(full), pe(pe), deploy(deploy), coff(coff) + { +- this->name = stem(this->pe); + this->is_exe = endswith(this->pe, ".exe"); +- this->def_file = this->name + ".def"; ++ std::string coff_path = stem(this->coff); ++ this->tmp_def_file = coff_path + "-tmp.def"; ++ this->def_file = coff_path + ".def"; + this->def_executor = ExecuteCommand("dumpbin.exe", {this->ComputeDefLine()}); + this->lib_executor = ExecuteCommand("lib.exe", {this->ComputeRenameLink()}); + } +@@ -992,11 +1090,11 @@ LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace, bool + * Creates the line to be provided to dumpbin.exe to produce the exports of a given + * dll in the case where we do not have access to the original link line + * +- * Produces something like `/EXPORTS ` + */ + std::string LibRename::ComputeDefLine() + { +- return "/EXPORTS " + this->pe; ++ return "/NOLOGO /EXPORTS " + this->coff; + } + + /** +@@ -1005,9 +1103,51 @@ std::string LibRename::ComputeDefLine() + * + * Returns the return code of the Def file computation operation + */ +-int LibRename::ComputeDefFile() ++bool LibRename::ComputeDefFile() + { +- return this->def_executor.Execute(this->def_file); ++ this->def_executor.Execute(this->tmp_def_file); ++ int res = this->def_executor.Join(); ++ if(res) { ++ return false; ++ } ++ // Need to process the produced def file because it's wrong ++ // Open input file ++ std::ifstream inputFile(this->tmp_def_file); ++ if (!inputFile.is_open()) { ++ std::cerr << "Error: Could not open input file " << tmp_def_file << std::endl; ++ return false; ++ } ++ ++ // Open output file ++ std::ofstream outputFile(this->def_file); ++ if (!outputFile.is_open()) { ++ std::cerr << "Error: Could not open output file " << this->def_file << std::endl; ++ return false; ++ } ++ ++ // Write the standard .def file header ++ // You might want to get the DLL name dynamically from the input filename or dumpbin output ++ outputFile << "EXPORTS\n"; ++ ++ std::string line; ++ // First 8 lines are templated output ++ // skip them ++ for (int i = 0; i < 8; ++i) { // Adjust this number if the header changes across dumpbin versions ++ if (!std::getline(inputFile, line)) break; ++ } ++ while (std::getline(inputFile, line)) { ++ if (line.empty()) { ++ continue; ++ } ++ else if(line.find("Summary") != std::string::npos) { // Skip header in export block if still present ++ break; ++ } ++ outputFile << " " << lstrip(line, " ") << std::endl; ++ } ++ inputFile.close(); ++ outputFile.close(); ++ std::remove(this->tmp_def_file.c_str()); ++ return true; + } + + /** +@@ -1037,19 +1177,23 @@ bool LibRename::ExecuteRename() + // recompute the .def and .lib for dlls + // exes do not typically have import libs so we don't handle + // that case +- if(!this->deploy && !this->is_exe){ ++ // We do not bother with defs for things that don't have ++ // import libraries ++ if(!this->deploy && !this->coff.empty()){ + // Extract DLL +- if(this->ComputeDefFile()) { ++ if(!this->ComputeDefFile()) { ++ debug("Failed to compute def file"); + return false; + } + if(!this->ExecuteLibRename()) { ++ debug("Failed to create and rename import lib"); + return false; + } + } + if (this->full) { + if(!this->ExecutePERename()) { + std::cerr << "Unable to execute rename of " +- "referenced components in PE file: " << this->name << "\n"; ++ "referenced components in PE file: " << this->pe << "\n"; + return false; + } + } +@@ -1069,20 +1213,24 @@ bool LibRename::ExecuteLibRename() + this->lib_executor.Execute(); + int ret_code = this->lib_executor.Join(); + if(ret_code != 0) { +- std::cerr << "Lib Rename failed" << reportLastError() << "\n"; ++ std::cerr << "Lib Rename failed with exit code: " << ret_code << "\n"; + return false; + } ++ // replace former .lib with renamed .lib ++ std::remove(this->coff.c_str()); ++ std::rename(this->new_lib.c_str(), this->coff.c_str()); + // import library has been generated with + // mangled abs path to dll - + // unmangle it +- CoffReaderWriter cr(this->new_lib); ++ CoffReaderWriter cr(this->coff); + CoffParser coff(&cr); + if (!coff.Parse()) { + std::cerr << "Unable to parse generated import library {" << this->new_lib <<"}\n"; + return false; + } +- if(!coff.NormalizeName(mangle_name(this->pe) )) { +- std::cerr << "Unable to normalize name\n"; ++ std::string mangledName = mangle_name(this->pe); ++ if(!coff.NormalizeName(mangledName)) { ++ std::cerr << "Unable to normalize name: " << mangledName << "\n"; + return false; + } + return true; +@@ -1094,10 +1242,10 @@ bool LibRename::ExecuteLibRename() + */ + bool LibRename::ExecutePERename() + { +- LPCWSTR lib_name = ConvertAnsiToWide(this->pe).c_str(); +- HANDLE pe_handle = CreateFileW(lib_name, (GENERIC_READ|GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ++ std::wstring pe_path = ConvertAnsiToWide(this->pe); ++ HANDLE pe_handle = CreateFileW(pe_path.c_str(), (GENERIC_READ|GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE){ +- std::cerr << "Unable to acquire file handle to "<< lib_name << ": " << reportLastError() << "\n"; ++ std::cerr << "Unable to acquire file handle to "<< pe_path.c_str() << ": " << reportLastError() << "\n"; + return false; + } + return this->FindDllAndRename(pe_handle); +@@ -1131,13 +1279,14 @@ std::string LibRename::ComputeRenameLink() + line += this->def_file + " "; + line += "-name:"; + line += mangle_name(this->pe) + " "; +- std::string name(stem(this->pe)); ++ std::string name(stem(this->coff)); + if (!this->replace){ + this->new_lib = name + ".abs-name.lib"; + } + else { +- this->new_lib = this->pe; ++ // Name must be different ++ this->new_lib = name+"-tmp.lib"; + } +- line += "-out:\""+ this->new_lib + "\"" + " " + this->pe; ++ line += "-out:\""+ this->new_lib + "\""; + return line; + } +diff --git a/src/winrpath.h b/src/winrpath.h +index d2657b5..e440982 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -92,8 +92,10 @@ typedef struct coff_member { + first_linker_member *first_link; + second_linker_member *second_link; + bool is_short; ++ bool is_longname; + coff_member() { + this->is_short = false; ++ this->is_longname = false; + this->first_link = NULL; + this->second_link = NULL; + this->short_member = NULL; +@@ -177,22 +179,28 @@ class CoffParser { + private: + CoffReaderWriter* coffStream; + coff coff; +- void ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member); ++ bool verified = false; ++ bool ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member); + void ParseShortImport(coff_member *member); + void ParseFullImport(coff_member *member); + void ParseFirstLinkerMember(coff_member *member); + void ParseSecondLinkerMember(coff_member *member); + void ReportLongImportMember(long_import_member *li); + void ReportShortImportMember(short_import_member *si); ++ void ReportLongName(char * data); + void NormalizeLinkerMember(const std::string &name, const int &base_offset, const int &offset, const char * strings, const DWORD symbols); + void NormalizeSectionNames(const std::string &name, char* section, const DWORD §ion_data_start_offset, int data_size); +- ++ bool ValidateLongName(coff_member *member, int size); ++ void writeRename(char *name, const int size, const int loc); ++ bool validateName(char *old_name, std::string new_name); + public: + CoffParser(CoffReaderWriter * cr); + ~CoffParser() = default; + bool Parse(); + bool NormalizeName(std::string &name); + void Report(); ++ int Verify(); ++ static int Validate(std::string &coff); + }; + + class LinkerInvocation { +@@ -205,10 +213,14 @@ public: + std::string get_name(); + std::string get_out(); + std::string get_mangled_out(); ++ std::string get_implib_name(); ++ std::string get_def_file(); + private: + std::string line; + StrList tokens; + std::string name; ++ std::string implibname; ++ std::string def_file; + std::string output; + StrList libs; + StrList objs; +@@ -218,11 +230,12 @@ private: + + class LibRename { + public: +- LibRename(std::string pe, bool full, bool deploy, bool replace, bool report); ++ LibRename(std::string pe, std::string coff, bool full, bool deploy, bool replace); ++ LibRename(std::string pe, bool full, bool deploy, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +- int ComputeDefFile(); ++ bool ComputeDefFile(); + std::string ComputeRenameLink(); + std::string ComputeDefLine(); + +@@ -233,14 +246,14 @@ private: + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +- std::string name; ++ std::string coff; + std::string new_lib; + std::string def_file; ++ std::string tmp_def_file; + bool full; + bool deploy; + bool replace; + bool is_exe; +- bool report; + }; + + +diff --git a/test/calc.h b/test/calc.h +deleted file mode 100644 +index d7d3975..0000000 +--- a/test/calc.h ++++ /dev/null +@@ -1,14 +0,0 @@ +-/** +- * Copyright Spack Project Developers. See COPYRIGHT file for details. +- * +- * SPDX-License-Identifier: (Apache-2.0 OR MIT) +- */ +-#pragma once +- +-#ifdef CALC_EXPORTS +-#define CALC_API __declspec(dllexport) +-#else +-#define CALC_API __declspec(dllimport) +-#endif +- +-extern "C" CALC_API int add(int a, int b); diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index eef1d7e1ad5..955c6a1ca7b 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -5,12 +5,14 @@ import shutil import sys -from spack_repo.builtin.build_systems.generic import Package +from spack_repo.builtin.build_systems.nmake import NMakePackage from spack.package import * +IS_WINDOWS = sys.platform == "win32" -class CompilerWrapper(Package): + +class CompilerWrapper(Package, NMakePackage): """Spack compiler wrapper script. Compiler commands go through this compiler wrapper in Spack builds. @@ -26,126 +28,84 @@ class CompilerWrapper(Package): 3. It provides a mechanism to inject flags from specs """ - homepage = "https://github.com/spack/spack" - url = "https://github.com/spack/compiler-wrapper/releases/download/v1.0/compiler-wrapper-1.0.tar.gz" + homepage_nix = "https://github.com/spack/spack" + url_nix = "https://github.com/spack/compiler-wrapper/releases/download/v1.0/compiler-wrapper-1.0.tar.gz" + + homepage_win = "https://github.com/spack/msvc-wrapper" + url_win = "https://github.com/spack/msvc-wrapper/archive/refs/tags/v0.1.0.tar.gz" + + + homepage = homepage_win if IS_WINDOWS else homepage_nix + url = url_win if IS_WINDOWS else url_nix # FIXME (compiler as nodes): use a different tag, since this is only to exclude # this node from auto-generated rules tags = ["runtime"] + depends_on("msvc", type="build") maintainers("haampie") license("Apache-2.0 OR MIT") + default_builder = "nmake" if IS_WINDOWS else "generic" + build_system("generic", conditional("nmake", when="platform=windows"), default=default_builder) - if sys.platform != "win32": + if not IS_WINDOWS: version("1.1.0", sha256="a07b35081d14b0729090bc1e5790a5dda2d5b997e064c62da39a1224ee249b2a") version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: - version("1.0") - has_code = False + version("0.1.0", sha256="4eab2cb48bb83edb88780517c9dfa55778f8adc555ec4939cb73e2d05fed5a5a") + + # available in 0.1.1 + patch("fixup11.patch", when="platform=windows") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get # their way to the default view return pathlib.Path(str(self.prefix)) / "libexec" / "spack" - def install(self, spec, prefix): - if sys.platform == "win32": - placeholder = self.bin_dir() / "placeholder-wrapper" - placeholder.parent.mkdir(parents=True) - placeholder.write_text( - "This file is a placeholder for the compiler wrapper on Windows." - ) - return + def setup_dependent_package(self, module, dependent_spec): + def _spack_compiler_attribute(*, language: str) -> str: + compiler_pkg = dependent_spec[language].package + if sys.platform != "win32": + # On non-Windows we return the appropriate path to the compiler wrapper + return str(self.bin_dir() / compiler_pkg.compiler_wrapper_link_paths[language]) - cc_script = pathlib.Path(self.stage.source_path) / "cc.sh" - bin_dir = self.bin_dir() + # On Windows we return the real compiler + if language == "c": + return compiler_pkg.cc + elif language == "cxx": + return compiler_pkg.cxx + elif language == "fortran": + return compiler_pkg.fortran - # Copy the script - bin_dir.mkdir(parents=True) - installed_script = bin_dir / "cc" - shutil.copy(cc_script, str(installed_script)) - set_executable(installed_script) + if dependent_spec.has_virtual_dependency("c"): + setattr(module, "spack_cc", _spack_compiler_attribute(language="c")) - # Create links to use the script under different names - for name in ( - "ld.lld", - "ld.gold", - "ld", - "ftn", - "fc", - "f95", - "f90", - "f77", - "cpp", - "c99", - "c89", - "c++", - ): - (bin_dir / name).symlink_to(installed_script) + if dependent_spec.has_virtual_dependency("cxx"): + setattr(module, "spack_cxx", _spack_compiler_attribute(language="cxx")) - for subdir, name in ( - ("aocc", "clang"), - ("aocc", "clang++"), - ("aocc", "flang"), - ("arm", "armclang"), - ("arm", "armclang++"), - ("arm", "armflang"), - ("case-insensitive", "CC"), - ("cce", "cc"), - ("cce", "craycc"), - ("cce", "crayftn"), - ("cce", "ftn"), - ("clang", "clang"), - ("clang", "clang++"), - ("clang", "flang"), - ("fj", "fcc"), - ("fj", "frt"), - ("gcc", "gcc"), - ("gcc", "g++"), - ("gcc", "gfortran"), - ("intel", "icc"), - ("intel", "icpc"), - ("intel", "ifort"), - ("nag", "nagfor"), - ("nvhpc", "nvc"), - ("nvhpc", "nvc++"), - ("nvhpc", "nvfortran"), - ("oneapi", "icx"), - ("oneapi", "icpx"), - ("oneapi", "ifx"), - ("rocmcc", "amdclang"), - ("rocmcc", "amdclang++"), - ("rocmcc", "amdflang"), - ("xl", "xlc"), - ("xl", "xlc++"), - ("xl", "xlf"), - ("xl", "xlf90"), - ("xl_r", "xlc_r"), - ("xl_r", "xlc++_r"), - ("xl_r", "xlf_r"), - ("xl_r", "xlf90_r"), - ): - (bin_dir / subdir).mkdir(exist_ok=True) - (bin_dir / subdir / name).symlink_to(installed_script) + if dependent_spec.has_virtual_dependency("fortran"): + setattr(module, "spack_fc", _spack_compiler_attribute(language="fortran")) + setattr(module, "spack_f77", _spack_compiler_attribute(language="fortran")) - # Extra symlinks for Cray - cray_dir = bin_dir / "cce" / "case-insensitive" - cray_dir.mkdir(exist_ok=True) - (cray_dir / "crayCC").symlink_to(installed_script) - (cray_dir / "CC").symlink_to(installed_script) + @property + def disable_new_dtags(self) -> str: + if self.spec.satisfies("platform=darwin"): + return "" + return "--disable-new-dtags" + + @property + def enable_new_dtags(self) -> str: + if self.spec.satisfies("platform=darwin"): + return "" + return "--enable-new-dtags" - # Extra symlink for Fujitsu - fj_dir = bin_dir / "fj" / "case-insensitive" - fj_dir.mkdir(exist_ok=True) - (fj_dir / "FCC").symlink_to(installed_script) +class EnvironmentSetup: def setup_dependent_build_environment( self, env: EnvironmentModifications, dependent_spec: Spec ) -> None: - if sys.platform == "win32": - return - + _var_list = [] if dependent_spec.has_virtual_dependency("c"): _var_list.append(("c", "cc", "CC", "SPACK_CC")) @@ -156,12 +116,11 @@ def setup_dependent_build_environment( if dependent_spec.has_virtual_dependency("fortran"): _var_list.append(("fortran", "fortran", "F77", "SPACK_F77")) _var_list.append(("fortran", "fortran", "FC", "SPACK_FC")) - # The package is not used as a compiler, so skip this setup if not _var_list: return - bin_dir = self.bin_dir() + bin_dir = self.pkg.bin_dir() implicit_rpaths, env_paths = [], [] extra_rpaths = [] for language, attr_name, wrapper_var_name, spack_var_name in _var_list: @@ -172,6 +131,9 @@ def setup_dependent_build_environment( compiler = getattr(compiler_pkg, attr_name) env.set(spack_var_name, compiler) + if hasattr(compiler_pkg, "ld"): + env.set("SPACK_LD", compiler_pkg.ld) + # -frandom-seed= is needed for deterministic builds with GCC if compiler_pkg.name == "gcc" and self.spec.satisfies("@1.1:"): env.set(f"SPACK_{wrapper_var_name}_HAS_FRANDOM_SEED", "1") @@ -229,45 +191,124 @@ def setup_dependent_build_environment( extra_rpaths = dedupe(extra_rpaths) env.set("SPACK_COMPILER_EXTRA_RPATHS", ":".join(extra_rpaths)) - env.set("SPACK_ENABLE_NEW_DTAGS", self.enable_new_dtags) - env.set("SPACK_DISABLE_NEW_DTAGS", self.disable_new_dtags) + env.set("SPACK_ENABLE_NEW_DTAGS", self.pkg.enable_new_dtags) + env.set("SPACK_DISABLE_NEW_DTAGS", self.pkg.disable_new_dtags) for item in env_paths: env.prepend_path("SPACK_COMPILER_WRAPPER_PATH", item) - def setup_dependent_package(self, module, dependent_spec): - def _spack_compiler_attribute(*, language: str) -> str: - compiler_pkg = dependent_spec[language].package - if sys.platform != "win32": - # On non-Windows we return the appropriate path to the compiler wrapper - return str(self.bin_dir() / compiler_pkg.compiler_wrapper_link_paths[language]) - # On Windows we return the real compiler - if language == "c": - return compiler_pkg.cc - elif language == "cxx": - return compiler_pkg.cxx - elif language == "fortran": - return compiler_pkg.fortran +class GenericBuilder(generic.GenericBuilder, EnvironmentSetup): - if dependent_spec.has_virtual_dependency("c"): - setattr(module, "spack_cc", _spack_compiler_attribute(language="c")) + def install(self, pkg, spec, prefix): + cc_script = pathlib.Path(self.stage.source_path) / "cc.sh" + bin_dir = pkg.bin_dir() - if dependent_spec.has_virtual_dependency("cxx"): - setattr(module, "spack_cxx", _spack_compiler_attribute(language="cxx")) + # Copy the script + bin_dir.mkdir(parents=True) + installed_script = bin_dir / "cc" + shutil.copy(cc_script, str(installed_script)) + set_executable(installed_script) - if dependent_spec.has_virtual_dependency("fortran"): - setattr(module, "spack_fc", _spack_compiler_attribute(language="fortran")) - setattr(module, "spack_f77", _spack_compiler_attribute(language="fortran")) + # Create links to use the script under different names + for name in ( + "ld.lld", + "ld.gold", + "ld", + "ftn", + "fc", + "f95", + "f90", + "f77", + "cpp", + "c99", + "c89", + "c++", + ): + (bin_dir / name).symlink_to(installed_script) - @property - def disable_new_dtags(self) -> str: - if self.spec.satisfies("platform=darwin"): - return "" - return "--disable-new-dtags" + for subdir, name in ( + ("aocc", "clang"), + ("aocc", "clang++"), + ("aocc", "flang"), + ("arm", "armclang"), + ("arm", "armclang++"), + ("arm", "armflang"), + ("case-insensitive", "CC"), + ("cce", "cc"), + ("cce", "craycc"), + ("cce", "crayftn"), + ("cce", "ftn"), + ("clang", "clang"), + ("clang", "clang++"), + ("clang", "flang"), + ("fj", "fcc"), + ("fj", "frt"), + ("gcc", "gcc"), + ("gcc", "g++"), + ("gcc", "gfortran"), + ("intel", "icc"), + ("intel", "icpc"), + ("intel", "ifort"), + ("nag", "nagfor"), + ("nvhpc", "nvc"), + ("nvhpc", "nvc++"), + ("nvhpc", "nvfortran"), + ("oneapi", "icx"), + ("oneapi", "icpx"), + ("oneapi", "ifx"), + ("rocmcc", "amdclang"), + ("rocmcc", "amdclang++"), + ("rocmcc", "amdflang"), + ("xl", "xlc"), + ("xl", "xlc++"), + ("xl", "xlf"), + ("xl", "xlf90"), + ("xl_r", "xlc_r"), + ("xl_r", "xlc++_r"), + ("xl_r", "xlf_r"), + ("xl_r", "xlf90_r"), + ): + (bin_dir / subdir).mkdir(exist_ok=True) + (bin_dir / subdir / name).symlink_to(installed_script) - @property - def enable_new_dtags(self) -> str: - if self.spec.satisfies("platform=darwin"): - return "" - return "--enable-new-dtags" + # Extra symlinks for Cray + cray_dir = bin_dir / "cce" / "case-insensitive" + cray_dir.mkdir(exist_ok=True) + (cray_dir / "crayCC").symlink_to(installed_script) + (cray_dir / "CC").symlink_to(installed_script) + + # Extra symlink for Fujitsu + fj_dir = bin_dir / "fj" / "case-insensitive" + fj_dir.mkdir(exist_ok=True) + (fj_dir / "FCC").symlink_to(installed_script) + + + +class NMakeBuilder(nmake.NMakeBuilder, EnvironmentSetup): + install_targets = ["install"] + build_targets = ["cl.exe"] + + def install(self, pkg, spec, prefix): + bin_dir = pkg.bin_dir() + opts = self.std_nmake_args + opts.append(self.define("PREFIX", str(bin_dir))) + with fs.working_dir(self.build_directory): + nmake(*opts, *self.install_targets, ignore_quotes=self.ignore_quotes) + + # Create links to use the script under different names + for name in ("link", "ftn", "fc", "f95", "f90", "f77", "cpp", "c99", "c89", "c++"): + (bin_dir / name).symlink_to(bin_dir / "cl.exe") + + + for subdir, name in ( + ("case-insensitive", "CC.exe"), + ("intel", "ifort.exe"), + ("oneapi", "ifx.exe"), + ("msvc", "cl.exe"), + ): + (bin_dir / subdir).mkdir(exist_ok=True) + (bin_dir / subdir / name).symlink_to(bin_dir / "cl.exe") + + + diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index 3f1fe49d72e..eff8b7ec9c3 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) import os.path +import pathlib import re import subprocess @@ -46,12 +47,11 @@ def install(self, spec, prefix): compiler_version_argument = "" compiler_version_regex = r"([1-9][0-9]*\.[0-9]*\.[0-9]*)" - # Due to the challenges of supporting compiler wrappers - # in Windows, we leave these blank, and dynamically compute - # based on proper versions of MSVC from there - # pending acceptance of #28117 for full support using - # compiler wrappers - compiler_wrapper_link_paths = {"c": "", "cxx": "", "fortran": ""} + compiler_wrapper_link_paths = { + "c": "msvc\\cl.exe", + "cxx": "msvc\\cl.exe", + "fortran": "oneapi\\ifx.exe", + } provides("c", "cxx", "fortran") requires("platform=windows", msg="MSVC is only supported on Windows") @@ -75,6 +75,17 @@ def determine_variants(cls, exes, version_str): # MSVC uses same executable for both languages spec, extras = super().determine_variants(exes, version_str) extras["compilers"]["c"] = extras["compilers"]["cxx"] + + # Spack's msvc compiler wrapper also wraps the linker + # to inject rpath-analogous behavior into Windows + # binaries. We define the linker so we can + # expose it to the run environmnet of the wrapper + # similar to how we do CC vs SPACK_CC + # the linker is always in the same directory as the compiler + extras["compilers"]["ld"] = str( + pathlib.Path(extras["compilers"]["cxx"]).parent / "link.exe" + ) + # This depends on oneapi being processed before msvc # which is guarunteed from detection behavior. # Processing oneAPI tracks oneAPI installations within @@ -125,14 +136,6 @@ def setup_dependent_build_environment( else: env.set_path(env_var, int_env[env_var].split(os.pathsep)) - if self.cc: - env.set("CC", self.cc) - if self.cxx: - env.set("CXX", self.cxx) - if self.fortran: - env.set("FC", self.fortran) - env.set("F77", self.fortran) - def init_msvc(self): # To use the MSVC compilers, VCVARS must be invoked # VCVARS is located at a fixed location, referencable @@ -264,6 +267,15 @@ def platform_toolset_ver(self): vs22_toolset = Version(toolset_ver) > Version("142") return toolset_ver if not vs22_toolset else "143" + @property + def ld(self): + assert self.spec.concrete, "cannot retrieve C++ linker, spec is not concrete" + assert ( + self.spec.external + ), "MSVC is external only, please report this bug to the Spack maintainers" + + return self.spec.extra_attributes.get("compilers", {}).get("ld", None) + class CmdCall: """Compose a call to `cmd` for an ordered series of cmd commands/scripts""" From a0d4db9c73f169d148b4172dc2352e460bafdc67 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 23 Jun 2025 11:12:23 -0400 Subject: [PATCH 03/81] Add latest wrapper patch --- .../packages/compiler_wrapper/package.py | 1 + .../compiler_wrapper/quote_args.patch | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 955c6a1ca7b..91773f8be3e 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,6 +57,7 @@ class CompilerWrapper(Package, NMakePackage): # available in 0.1.1 patch("fixup11.patch", when="platform=windows") + patch("quote_args.patch", when="platform=windows") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch new file mode 100644 index 00000000000..eb75b1a7c51 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch @@ -0,0 +1,60 @@ +diff --git a/Makefile b/Makefile +index c90ccce..31fcd5f 100644 +--- a/Makefile ++++ b/Makefile +@@ -22,7 +22,7 @@ PREFIX="$(MAKEDIR)\install\" + !ENDIF + + !IF "$(BUILD_TYPE)" == "DEBUG" +-BUILD_CFLAGS = /Zi /fsanitize=address ++BUILD_CFLAGS = /Zi + BUILD_LINK = /DEBUG + !ENDIF + +@@ -59,7 +59,7 @@ setup_test: cl.exe + # smoke test - can the wrapper compile anything + build_and_check_test_sample : setup_test + cd tmp\test +- cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS /I ..\..\test\include ++ cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER=\"calc.h\" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include + link $(LFLAGS) calc.obj /out:calc.dll /DLL + link $(LFLAGS) main.obj calc.lib /out:tester.exe +diff --git a/src/utils.cxx b/src/utils.cxx +index 8e153b0..220be6f 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -188,6 +188,13 @@ std::string quoteAsNeeded(std::string &str) { + // There are spaces or special characters in string, quote it + return "\"" + str + "\""; + } ++ if (str.find_first_of("\"") != std::string::npos) { ++ // If there are escaped quotes in input ++ // We need to escape them as well as we're adding another ++ // layer of indirection between builder and compiler ++ std::regex pattern("\""); ++ return std::regex_replace(str, pattern, "\\\""); ++ } + return str; + } + +diff --git a/test/calc.cxx b/test/src file/calc.cxx +similarity index 73% +rename from test/calc.cxx +rename to test/src file/calc.cxx +index cd7a136..b07cca2 100644 +--- a/test/calc.cxx ++++ b/test/src file/calc.cxx +@@ -3,7 +3,12 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++ ++#if defined(CALC_HEADER) ++#include CALC_HEADER /* "calc.h" */ ++#else + #include "calc.h" ++#endif + + extern "C" int add(int a, int b) + { From dfbdbbff3c0d489e7244888e5210df3c826e3a6c Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 23 Jun 2025 14:01:43 -0400 Subject: [PATCH 04/81] regex update --- .../packages/compiler_wrapper/include_regex.patch | 12 ++++++++++++ .../builtin/packages/compiler_wrapper/package.py | 1 + 2 files changed, 13 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch new file mode 100644 index 00000000000..3191e85c040 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch @@ -0,0 +1,12 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index 220be6f..f6311c6 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -12,6 +12,7 @@ + #include + #include + #include "shlwapi.h" ++#include + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 91773f8be3e..d55f39dd419 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -58,6 +58,7 @@ class CompilerWrapper(Package, NMakePackage): # available in 0.1.1 patch("fixup11.patch", when="platform=windows") patch("quote_args.patch", when="platform=windows") + patch("include_regex.patch", when="platform=windows") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From d611e61f95f1326bd8ded3b5472c86950c3da358 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 17 Jul 2025 10:14:22 -0400 Subject: [PATCH 05/81] Compiler Wrapper: update Windows patches --- .../packages/compiler_wrapper/package.py | 2 + .../packages/compiler_wrapper/pipe.patch | 221 ++++++++++++++++++ .../packages/compiler_wrapper/rsp.patch | 62 +++++ 3 files changed, 285 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index d55f39dd419..e5eff538169 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,6 +59,8 @@ class CompilerWrapper(Package, NMakePackage): patch("fixup11.patch", when="platform=windows") patch("quote_args.patch", when="platform=windows") patch("include_regex.patch", when="platform=windows") + patch("pipe.patch", when="platform=windows") + patch("rsp.patch", when="platform=windows") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch new file mode 100644 index 00000000000..fcf4c975ba0 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch @@ -0,0 +1,221 @@ +diff --git a/Makefile b/Makefile +index bb284d2..0a6186f 100644 +--- a/Makefile ++++ b/Makefile +@@ -108,11 +108,28 @@ test_relocate_dll: build_and_check_test_sample + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe ++ cd ../.. ++ ++test_pipe_overflow: build_and_check_test_sample ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ ++build_zerowrite_test: test\writezero.obj ++ link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ ++ ++test_zerowrite: build_zerowrite_test ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\writezero.exe ++ cl /c EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% + + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll ++test: test_wrapper test_relocate_exe test_relocate_dll test_pipe_overflow + + + clean : clean-test clean-cl +@@ -128,4 +145,5 @@ clean-cl : + del cl.exe + + clean-test: +- rmdir /q /s tmp ++ -@ if EXIST "tmp" rmdir /q /s "tmp" ++ +diff --git a/src/execute.cxx b/src/execute.cxx +index 77d49a6..0819d81 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -161,7 +161,7 @@ int ExecuteCommand::PipeChildToStdErr() + CHAR chBuf[BUFSIZE]; + BOOL bSuccess = TRUE; + HANDLE hParentOut; +- if (this->write_to_file) { ++ if (this->write_to_file && this->fileout != INVALID_HANDLE_VALUE) { + hParentOut = this->fileout; + } + else { +@@ -171,10 +171,35 @@ int ExecuteCommand::PipeChildToStdErr() + for (;;) + { + bSuccess = ReadFile( this->ChildStdErr_Rd, chBuf, BUFSIZE, &dwRead, NULL); +- if( ! bSuccess || dwRead == 0 ) break; +- +- bSuccess = WriteFile(hParentOut, chBuf, +- dwRead, &dwWritten, NULL); ++ // For an explanation behind the use of termianted here ++ // see the docstring on pipechildtostdout ++ if( ! bSuccess || (dwRead == 0 && this->terminated) ) break; ++ if(dwRead != 0) { ++ bSuccess = WriteFile(hParentOut, chBuf, ++ dwRead, &dwWritten, NULL); ++ if (dwWritten < dwRead && bSuccess){ ++ // incomplete write but not a failure ++ // since bSuccess is true ++ // So lets write until bSuccess is false or ++ // until all bytes are written ++ int currentPos = dwWritten; ++ while((dwWritten < dwRead) || dwWritten == 0) { ++ dwRead = dwRead - dwWritten; ++ CHAR * partialBuf = new CHAR[dwRead]; ++ for(int i = 0; i < dwRead; ++i) { ++ partialBuf[i] = chBuf[currentPos + i]; ++ } ++ bSuccess = WriteFile(hParentOut, partialBuf, ++ dwRead, &dwWritten, NULL); ++ delete partialBuf; ++ if (! bSuccess) break; ++ currentPos += dwWritten; ++ } ++ } ++ if (! bSuccess ) { ++ break; ++ } ++ } + if (! bSuccess ) break; + } + return !bSuccess; +@@ -192,7 +217,7 @@ int ExecuteCommand::PipeChildToStdout() + CHAR chBuf[BUFSIZE]; + BOOL bSuccess = TRUE; + HANDLE hParentOut; +- if (this->write_to_file) { ++ if (this->write_to_file && this->fileout != INVALID_HANDLE_VALUE) { + hParentOut = this->fileout; + } + else { +@@ -202,10 +227,41 @@ int ExecuteCommand::PipeChildToStdout() + for (;;) + { + bSuccess = ReadFile( this->ChildStdOut_Rd, chBuf, BUFSIZE, &dwRead, NULL); +- if( ! bSuccess || dwRead == 0 ) break; +- +- bSuccess = WriteFile(hParentOut, chBuf, +- dwRead, &dwWritten, NULL); ++ // Typically dwRead == 0 indicates the writer end of the pipe has ceased writing ++ // however if the writer were to invoke WriteFile with a size of 0, dwRead would ++ // be 0 but the writer would not have terminated. ++ // As such we need an explicit indication the writer process has termianted. ++ // From the MSVC docs: ++ // If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, ++ // the other end of the pipe called the WriteFile function with nNumberOfBytesToWrite ++ // set to zero. ++ if( ! bSuccess || (dwRead == 0 && this->terminated) ) break; ++ if(dwRead != 0){ ++ bSuccess = WriteFile(hParentOut, chBuf, ++ dwRead, &dwWritten, NULL); ++ if (dwWritten < dwRead && bSuccess){ ++ // incomplete write but not a failure ++ // since bSuccess is true ++ // So lets write until bSuccess is false or ++ // until all bytes are written ++ int currentPos = dwWritten; ++ while((dwWritten < dwRead) || dwWritten == 0) { ++ dwRead = dwRead - dwWritten; ++ CHAR * partialBuf = new CHAR[dwRead]; ++ for(int i = 0; i < dwRead; ++i) { ++ partialBuf[i] = chBuf[currentPos + i]; ++ } ++ bSuccess = WriteFile(hParentOut, partialBuf, ++ dwRead, &dwWritten, NULL); ++ delete partialBuf; ++ if (! bSuccess) break; ++ currentPos += dwWritten; ++ } ++ } ++ if (! bSuccess ) { ++ break; ++ } ++ } + if (! bSuccess ) break; + } + return !bSuccess; +@@ -242,6 +298,7 @@ int ExecuteCommand::ReportExitCode() + if(exit_code != STILL_ACTIVE) + break; + } ++ this->terminated = true; + CloseHandle(this->procInfo.hProcess); + return exit_code; + } +@@ -294,9 +351,17 @@ bool ExecuteCommand::Execute(const std::string &filename) + */ + int ExecuteCommand::Join() + { ++ // Allow primary command to conclude ++ // ensures stdout and stderr readers ++ // exit only once primary command process ++ // has concluded ++ // The child and std err pipe readers ++ // will not terminate under normal conditions ++ // unless this process concludes and sets the terminate flag ++ int commandError = this->exit_code_future.get(); + if(!this->child_out_future.get()) + return -999; + if(!this->child_err_future.get()) + return -999; +- return this->exit_code_future.get(); ++ return commandError; + } +diff --git a/src/execute.h b/src/execute.h +index 94c5a74..edb360b 100644 +--- a/src/execute.h ++++ b/src/execute.h +@@ -61,8 +61,9 @@ private: + SECURITY_ATTRIBUTES saAttr; + SECURITY_ATTRIBUTES saAttrErr; + HANDLE fileout = INVALID_HANDLE_VALUE; +- bool write_to_file; ++ bool write_to_file = false; + bool cpw_initalization_failure = false; ++ bool terminated = false; + std::string base_command; + StrList command_args; + }; +diff --git a/test/lots-of-output.bat b/test/lots-of-output.bat +new file mode 100644 +index 0000000..878528d +--- /dev/null ++++ b/test/lots-of-output.bat +@@ -0,0 +1,3 @@ ++@echo OFF ++ ++for /l %%x in (1, 1, 1250) do echo Test boilerplate output to fill stdout line number %%x +diff --git a/test/writezero/writezero.cxx b/test/writezero/writezero.cxx +new file mode 100644 +index 0000000..b9ed88a +--- /dev/null ++++ b/test/writezero/writezero.cxx +@@ -0,0 +1,14 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define BUFSIZE 100 ++ ++int main(int argc, char ** argv) { ++ HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE); ++ char buff[BUFSIZE]; ++ WriteFile(stdOut, buff, 0, NULL, NULL); ++} +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch new file mode 100644 index 00000000000..508f35b1b00 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch @@ -0,0 +1,62 @@ +diff --git a/src/ld.cxx b/src/ld.cxx +index 7cbe9fb..16a17f2 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -43,6 +43,7 @@ int LdInvocation::InvokeToolchain() + this->rpath_executor = ExecuteCommand("lib.exe", this->ComposeCommandLists( + { + {def_line, "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, ++ {link_run.get_rsp_file()}, + this->obj_args, + this->lib_args, + this->lib_dir_args, +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index 8821c8a..1a59fd4 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -190,6 +190,13 @@ void LinkerInvocation::Parse() + StrList defLine = split(*token, ":"); + this->def_file = defLine[1]; + } ++ else if (startswith(normalToken, "@") && endswith(normalToken, ".rsp")) { ++ // RSP files are used to describe object files, libraries, other CLI ++ // Switches relevant to the tool the rsp file is being passed to ++ // Primarily utilized by CMake and MSBuild projects to bypass ++ // Command line length limits ++ this->rsp_file = *token; ++ } + } + std::string ext = this->is_exe ? ".exe" : ".dll"; + if (this->output.empty()){ +@@ -216,6 +223,11 @@ std::string LinkerInvocation::get_def_file() + return this->def_file; + } + ++std::string LinkerInvocation::get_rsp_file() ++{ ++ return this->rsp_file; ++} ++ + std::string LinkerInvocation::get_out() + { + return this->output; +diff --git a/src/winrpath.h b/src/winrpath.h +index 9404a1f..1edb45c 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -215,12 +215,15 @@ public: + std::string get_mangled_out(); + std::string get_implib_name(); + std::string get_def_file(); ++ std::string get_rsp_file(); ++ + private: + std::string line; + StrList tokens; + std::string name; + std::string implibname; + std::string def_file; ++ std::string rsp_file; + std::string output; + StrList libs; + StrList objs; From b41c943fc25eb83e4d54647b7256e6bc4f37fa91 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 23 Jul 2025 09:43:37 -0400 Subject: [PATCH 06/81] Compiler wrapper: add new win patches --- .../packages/compiler_wrapper/package.py | 4 + .../compiler_wrapper/path_fixup.patch | 24 ++ .../packages/compiler_wrapper/paths.patch | 325 ++++++++++++++++++ .../spack-installed-libs.patch | 46 +++ .../packages/compiler_wrapper/strip_pad.patch | 35 ++ 5 files changed, 434 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index e5eff538169..4ea75a07b23 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -61,6 +61,10 @@ class CompilerWrapper(Package, NMakePackage): patch("include_regex.patch", when="platform=windows") patch("pipe.patch", when="platform=windows") patch("rsp.patch", when="platform=windows") + patch("paths.patch", when="platform=windows") + patch("path_fixup.patch", when="platform=windows") + patch("spack-installed-libs.patch", when="platform=windows") + patch("strip_pad.patch", when="platform=windows") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch new file mode 100644 index 00000000000..bdf8f0ecfb1 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch @@ -0,0 +1,24 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index e642c29..b026757 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -451,7 +451,7 @@ void replace_path_characters(char in[], int len) + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) ++char * pad_path(const char *pth, DWORD str_size, DWORD bsize) + { + size_t extended_buf = bsize - str_size + 2; + char * padded_path = new char[bsize+1]; +@@ -524,7 +524,9 @@ std::string mangle_name(const std::string &name) + * \param name string to check for path characters + */ + bool hasPathCharacters(const std::string &name) { +- for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ ++ typedef std::map::const_iterator PathCharMap; ++ for(PathCharMap it = path_to_special_characters.begin(); ++ it != path_to_special_characters.end(); ++it){ + if(!(name.find(it->first) == std::string::npos)){ + return true; + } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch new file mode 100644 index 00000000000..8f680fb7173 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch @@ -0,0 +1,325 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index 220be6f..e642c29 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -408,6 +408,131 @@ std::string reportLastError() + return std::system_category().message(error); + } + ++/** ++ * Replaces characters used to mangle path characters with ++ * valid path characters ++ * ++ * \param in a pointer to the string to replace the mangled path characters in ++ * \param len the length of the mangled path ++ */ ++void replace_special_characters(char in[], int len) ++{ ++ for (int i = 0; i < len; ++i) { ++ if (special_character_to_path.count(in[i])) ++ { ++ in[i] = special_character_to_path.at(in[i]); ++ } ++ ++ } ++} ++ ++/** ++ * Replaces path characters with special, non path, replacement characters ++ * ++ * \param in a pointer to the string to have its path characters replace with special placeholders ++ * \param len the length of the path to be mangled ++ */ ++void replace_path_characters(char in[], int len) ++{ ++ for (int i = 0; i < len; i++ ) { ++ if (path_to_special_characters.count(in[i])) ++ in[i] = path_to_special_characters.at(in[i]); ++ } ++} ++ ++/** ++ * Pads a given path with an amount of padding of special characters ++ * Paths are padded after the drive separator but before any path ++ * characters, i.e. C:[\\\\\\\]\path\to\exe with the section in [] ++ * being the padded component ++ * ++ * \param pth a pointer to the path to be padded ++ * \param str_size the length of the path - not including any ++ * null terminators. ++ * \param bsize the lengh of the padding to add ++ */ ++char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) ++{ ++ size_t extended_buf = bsize - str_size + 2; ++ char * padded_path = new char[bsize+1]; ++ for(int i = 0, j = 0; i < bsize && j < str_size; ++i){ ++ if(i < 2){ ++ padded_path[i] = pth[j]; ++ ++j; ++ } ++ else if(i < extended_buf){ ++ padded_path[i] = '|'; ++ } ++ else{ ++ padded_path[i] = pth[j]; ++ ++j; ++ } ++ } ++ padded_path[bsize] = '\0'; ++ return padded_path; ++} ++ ++/** ++ * Given a padded library path, return how much the path ++ * has been padded ++ * ++ * \param name the path for which to determine pad count ++ */ ++int get_padding_length(const std::string &name) ++{ ++ int c = 0; ++ std::string::const_iterator p = name.cbegin(); ++ p+=2; ++ while(p != name.end() && *p == '\\') { ++ ++c; ++ ++p; ++ } ++ return c; ++} ++ ++/** ++ * Mangles a string representing a path to have no path characters ++ * instead path characters (i.e. \\, :, etc) are replaced with ++ * special replacement characters ++ * ++ * \param name the string to be mangled ++ */ ++std::string mangle_name(const std::string &name) ++{ ++ std::string abs_out; ++ std::string mangled_abs_out; ++ if(IsPathAbsolute(name)){ ++ abs_out = name; ++ } ++ else{ ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ abs_out = join({GetCWD(), name}, "\\"); ++ } ++ char * chr_abs_out = new char [abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ replace_path_characters(chr_abs_out, abs_out.length()); ++ char * padded_path = pad_path(chr_abs_out, abs_out.length()); ++ mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); ++ ++ delete chr_abs_out; ++ delete padded_path; ++ return mangled_abs_out; ++} ++ ++/** ++ * Determines whether a string contains path characters ++ * \param name string to check for path characters ++ */ ++bool hasPathCharacters(const std::string &name) { ++ for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ ++ if(!(name.find(it->first) == std::string::npos)){ ++ return true; ++ } ++ } ++ return false; ++} ++ ++ + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} + + std::string LibraryFinder::FindLibrary(const std::string &lib_name, const std::string &lib_path) { +diff --git a/src/utils.h b/src/utils.h +index 7fbde84..86a9433 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -135,6 +135,18 @@ std::string GetCWD(); + // Returns boolean indication whether pth is absolute + bool IsPathAbsolute(const std::string &pth); + ++bool hasPathCharacters(const std::string &name); ++ ++std::string mangle_name(const std::string &name); ++ ++int get_padding_length(const std::string &name); ++ ++char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++ ++void replace_path_characters(char in[], int len); ++ ++void replace_special_characters(char in[], int len); ++ + // File and File handle helpers // + + // Returns File offset given RVA +@@ -184,4 +196,16 @@ public: + void EvalSearchPaths(); + }; + ++const std::map special_character_to_path{ ++ {'|', '\\'}, ++ {';', ':'} ++}; ++ ++const std::map path_to_special_characters{ ++ {'\\', '|'}, ++ {'/', '|'}, ++ {':', ';'} ++}; ++ ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index 1a59fd4..e19e53f 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -14,127 +14,6 @@ + using CoffMembers = std::vector; + + +-const std::map special_character_to_path{ +- {'|', '\\'}, +- {';', ':'} +-}; +- +-const std::map path_to_special_characters{ +- {'\\', '|'}, +- {'/', '|'}, +- {':', ';'} +-}; +- +-/** +- * Replaces characters used to mangle path characters with +- * valid path characters +- * +- * \param in a pointer to the string to replace the mangled path characters in +- * \param len the length of the mangled path +- */ +-void replace_special_characters(char in[], int len) +-{ +- for (int i = 0; i < len; ++i) { +- if (special_character_to_path.count(in[i])) +- { +- in[i] = special_character_to_path.at(in[i]); +- } +- +- } +-} +- +-/** +- * Replaces path characters with special, non path, replacement characters +- * +- * \param in a pointer to the string to have its path characters replace with special placeholders +- * \param len the length of the path to be mangled +- */ +-void replace_path_characters(char in[], int len) +-{ +- for (int i = 0; i < len; i++ ) { +- if (path_to_special_characters.count(in[i])) +- in[i] = path_to_special_characters.at(in[i]); +- } +-} +- +-/** +- * Pads a given path with an amount of padding of special characters +- * Paths are padded after the drive separator but before any path +- * characters, i.e. C:[\\\\\\\]\path\to\exe with the section in [] +- * being the padded component +- * +- * \param pth a pointer to the path to be padded +- * \param str_size the length of the path - not including any +- * null terminators. +- * \param bsize the lengh of the padding to add +- */ +-char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) +-{ +- size_t extended_buf = bsize - str_size + 2; +- char * padded_path = new char[bsize+1]; +- for(int i = 0, j = 0; i < bsize && j < str_size; ++i){ +- if(i < 2){ +- padded_path[i] = pth[j]; +- ++j; +- } +- else if(i < extended_buf){ +- padded_path[i] = '|'; +- } +- else{ +- padded_path[i] = pth[j]; +- ++j; +- } +- } +- padded_path[bsize] = '\0'; +- return padded_path; +-} +- +-/** +- * Given a padded library path, return how much the path +- * has been padded +- * +- * \param name the path for which to determine pad count +- */ +-int get_padding_length(const std::string &name) +-{ +- int c = 0; +- std::string::const_iterator p = name.cbegin(); +- p+=2; +- while(p != name.end() && *p == '\\') { +- ++c; +- ++p; +- } +- return c; +-} +- +-/** +- * Mangles a string representing a path to have no path characters +- * instead path characters (i.e. \\, :, etc) are replaced with +- * special replacement characters +- * +- * \param name the string to be mangled +- */ +-std::string mangle_name(const std::string &name) +-{ +- std::string abs_out; +- std::string mangled_abs_out; +- if(IsPathAbsolute(name)){ +- abs_out = name; +- } +- else{ +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } +- char * chr_abs_out = new char [abs_out.length() + 1]; +- strcpy(chr_abs_out, abs_out.c_str()); +- replace_path_characters(chr_abs_out, abs_out.length()); +- char * padded_path = pad_path(chr_abs_out, abs_out.length()); +- mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); +- +- delete chr_abs_out; +- delete padded_path; +- return mangled_abs_out; +-} + + /** + * Parses the command line of a given linker invocation and stores information +@@ -888,16 +767,6 @@ bool reportCoff(CoffParser &coff) + return true; + } + +- +-bool hasPathCharacters(const std::string &name) { +- for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ +- if(!(name.find(it->first) == std::string::npos)){ +- return true; +- } +- } +- return false; +-} +- + /* + * Checks a DLL name for special characters, if we're deploying, a path character, if we're + * relocating a spack sigil diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch new file mode 100644 index 00000000000..69e7a749bcd --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch @@ -0,0 +1,46 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index b026757..764dda6 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -534,6 +534,14 @@ bool hasPathCharacters(const std::string &name) { + return false; + } + ++bool SpackInstalledLib(const std::string &lib) { ++ const std::string prefix = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ if (prefix.empty()) { ++ debug("Unable to determine Spack install prefix, SPACK_INSTALL_PREFIX unset"); ++ return false; ++ } ++ return startswith(lib, prefix); ++} + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} + +diff --git a/src/utils.h b/src/utils.h +index 86a9433..a0accda 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -147,6 +147,8 @@ void replace_path_characters(char in[], int len); + + void replace_special_characters(char in[], int len); + ++bool SpackInstalledLib(const std::string &lib); ++ + // File and File handle helpers // + + // Returns File offset given RVA +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index e19e53f..73fad30 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -829,6 +829,9 @@ bool LibRename::RenameDll(char* name_loc, const std::string &dll_path) + } + } + else { ++ if(SpackInstalledLib(dll_path)) { ++ return true; ++ } + std::string file_name = basename(dll_path); + if(file_name.empty()) { + std::cerr << "Unable to extract filename from dll for relocation" << "\n"; diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch new file mode 100644 index 00000000000..07171edeb50 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch @@ -0,0 +1,35 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index 764dda6..9f52350 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -490,6 +490,20 @@ int get_padding_length(const std::string &name) + return c; + } + ++std::string strip_padding(const std::string &lib) ++{ ++ // One of the padding characters is a legitimate ++ // path separator ++ int pad_len = get_padding_length(lib)-1; ++ // Capture the drive and drive separator ++ std::string::const_iterator p = lib.cbegin(); ++ std::string::const_iterator e = lib.cbegin()+2; ++ std::string stripped_drive(p, e); ++ e = e + pad_len; ++ std::string path_remainder(e, lib.end()); ++ return stripped_drive + path_remainder; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -540,7 +554,8 @@ bool SpackInstalledLib(const std::string &lib) { + debug("Unable to determine Spack install prefix, SPACK_INSTALL_PREFIX unset"); + return false; + } +- return startswith(lib, prefix); ++ std::string stripped_lib = strip_padding(lib); ++ startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} From 4561563a51561a7182bc2817ec96709e5f42ec58 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 28 Aug 2025 15:28:08 -0400 Subject: [PATCH 07/81] Update post package api consolidation Signed-off-by: John Parent --- .../builtin/packages/compiler_wrapper/package.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 4ea75a07b23..491d761b298 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -5,7 +5,7 @@ import shutil import sys -from spack_repo.builtin.build_systems.nmake import NMakePackage +from spack_repo.builtin.build_systems.nmake import NMakePackage, NMakeBuilder from spack.package import * @@ -206,7 +206,7 @@ def setup_dependent_build_environment( env.prepend_path("SPACK_COMPILER_WRAPPER_PATH", item) -class GenericBuilder(generic.GenericBuilder, EnvironmentSetup): +class GenericBuilder(GenericBuilder, EnvironmentSetup): def install(self, pkg, spec, prefix): cc_script = pathlib.Path(self.stage.source_path) / "cc.sh" @@ -293,7 +293,7 @@ def install(self, pkg, spec, prefix): -class NMakeBuilder(nmake.NMakeBuilder, EnvironmentSetup): +class NMakeBuilder(NMakeBuilder, EnvironmentSetup): install_targets = ["install"] build_targets = ["cl.exe"] @@ -301,7 +301,7 @@ def install(self, pkg, spec, prefix): bin_dir = pkg.bin_dir() opts = self.std_nmake_args opts.append(self.define("PREFIX", str(bin_dir))) - with fs.working_dir(self.build_directory): + with working_dir(self.build_directory): nmake(*opts, *self.install_targets, ignore_quotes=self.ignore_quotes) # Create links to use the script under different names From 3a77a159863017a7e399a30052a855b4757a6456 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 3 Sep 2025 09:50:32 -0400 Subject: [PATCH 08/81] Compiler wrapper add develop temporarily for testing Signed-off-by: John Parent --- .../packages/compiler_wrapper/package.py | 26 +++++++++----- .../packages/compiler_wrapper/quoting.patch | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 491d761b298..c4258220ca6 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -33,10 +33,13 @@ class CompilerWrapper(Package, NMakePackage): homepage_win = "https://github.com/spack/msvc-wrapper" url_win = "https://github.com/spack/msvc-wrapper/archive/refs/tags/v0.1.0.tar.gz" + git_win = "https://github.com/spack/msvc-wrapper.git" homepage = homepage_win if IS_WINDOWS else homepage_nix url = url_win if IS_WINDOWS else url_nix + if IS_WINDOWS: + git = git_win # FIXME (compiler as nodes): use a different tag, since this is only to exclude # this node from auto-generated rules @@ -53,18 +56,23 @@ class CompilerWrapper(Package, NMakePackage): version("1.1.0", sha256="a07b35081d14b0729090bc1e5790a5dda2d5b997e064c62da39a1224ee249b2a") version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: + version("develop", branch="main") version("0.1.0", sha256="4eab2cb48bb83edb88780517c9dfa55778f8adc555ec4939cb73e2d05fed5a5a") + with when("@develop platform=windows"): + patch("quoting.patch") + # available in 0.1.1 - patch("fixup11.patch", when="platform=windows") - patch("quote_args.patch", when="platform=windows") - patch("include_regex.patch", when="platform=windows") - patch("pipe.patch", when="platform=windows") - patch("rsp.patch", when="platform=windows") - patch("paths.patch", when="platform=windows") - patch("path_fixup.patch", when="platform=windows") - patch("spack-installed-libs.patch", when="platform=windows") - patch("strip_pad.patch", when="platform=windows") + with when("@0.1.0 platform=windows"): + patch("fixup11.patch") + patch("quote_args.patch") + patch("include_regex.patch") + patch("pipe.patch") + patch("rsp.patch") + patch("paths.patch") + patch("path_fixup.patch") + patch("spack-installed-libs.patch") + patch("strip_pad.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch new file mode 100644 index 00000000000..ca135eff345 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch @@ -0,0 +1,35 @@ +diff --git a/src/utils.cxx b/src/utils.cxx +index aa65bcb..a22dcd5 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -211,16 +211,23 @@ void lower(std::string& str) { + } + + std::string quoteAsNeeded(std::string& str) { +- if (str.find_first_of(" &<>|()") != std::string::npos) { +- // There are spaces or special characters in string, quote it +- return "\"" + str + "\""; +- } ++ // Note: the ordering if these two conditionals is important ++ // If the second conditional is executed first, the first ++ // will always be true as the second injects the string ++ // on which the first is conditioned ++ // Basically: If we find escaped strings: escape em again ++ // If we find space/special chars: escape the whole string ++ + if (str.find_first_of('\"') != std::string::npos) { + // If there are escaped quotes in input + // We need to escape them as well as we're adding another +- // layer of indirection between builder and compiler ++ // layer of indirection between caller and compiler + std::regex const pattern("\""); +- return std::regex_replace(str, pattern, "\\\""); ++ str = std::regex_replace(str, pattern, "\\\""); ++ } ++ if (str.find_first_of(" &<>|()") != std::string::npos) { ++ // There are spaces or special characters in string, quote it ++ str = "\"" + str + "\""; + } + return str; + } + From f9d77a4d982904033e727725b1bb46ad93e02333 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 9 Sep 2025 13:52:48 -0400 Subject: [PATCH 09/81] compiler-wrapper: add long name support patch Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index c4258220ca6..a0998919bd6 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -61,7 +61,7 @@ class CompilerWrapper(Package, NMakePackage): with when("@develop platform=windows"): patch("quoting.patch") - + patch("long_name.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): patch("fixup11.patch") From 912358efd9260b6338939d81a90c2c0e9f48ed2e Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 9 Sep 2025 13:52:56 -0400 Subject: [PATCH 10/81] Actually add patch Signed-off-by: John Parent --- .../packages/compiler_wrapper/long_name.patch | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch new file mode 100644 index 00000000000..4193b613db2 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch @@ -0,0 +1,261 @@ +diff --git a/src/ld.cxx b/src/ld.cxx +index ff9bdde..890f364 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,7 @@ + #include "ld.h" + #include + #include ++#include + #include "coff_parser.h" + #include "coff_reader_writer.h" + #include "linker_invocation.h" +@@ -34,7 +35,14 @@ DWORD LdInvocation::InvokeToolchain() { + // We're creating a dll, we need to create an appropriate import lib + if (!link_run.IsExeLink()) { + std::string const imp_lib_name = link_run.get_implib_name(); +- std::string dll_name = link_run.get_mangled_out(); ++ std::string dll_name; ++ try { ++ dll_name = link_run.get_mangled_out(); ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Unable to mangle PE " << link_run.get_out() ++ << " name is too long\n"; ++ return ExitConditions::NORMALIZE_NAME_FAILURE; ++ } + std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; + std::string def = "-def "; + std::string piped_args = link_run.get_lib_link_args(); +diff --git a/src/main.cxx b/src/main.cxx +index 9458d17..26d940d 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -89,12 +89,19 @@ int main(int argc, const char* argv[]) { + } + DEBUG = debug; + std::unique_ptr rpath_lib; +- if (has_coff) { +- rpath_lib = std::make_unique( +- patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); +- } else { +- rpath_lib = std::make_unique(patch_args.at("pe"), full, +- deploy, true); ++ try { ++ if (has_coff) { ++ rpath_lib = std::make_unique(patch_args.at("pe"), ++ patch_args.at("coff"), ++ full, deploy, true); ++ } else { ++ rpath_lib = std::make_unique(patch_args.at("pe"), ++ full, deploy, true); ++ } ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Cannot Rename PE file " << patch_args.at("pe") ++ << " it contains references that are too long.\n"; ++ return ExitConditions::RENAME_FAILURE; + } + if (!rpath_lib->ExecuteRename()) { + std::cerr << "Library rename failed\n"; +@@ -110,9 +117,17 @@ int main(int argc, const char* argv[]) { + return ExitConditions::CLI_FAILURE; + } + if (report_args.find("pe") != report_args.end()) { +- LibRename portable_executable(report_args.at("pe"), std::string(), +- false, false, true); +- portable_executable.ExecuteRename(); ++ try { ++ LibRename portable_executable( ++ report_args.at("pe"), std::string(), false, false, true); ++ portable_executable.ExecuteRename(); ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr ++ << "Unable to parse command line for reporting\n" ++ << "run command with --help flag for accepted command " ++ "line arguments\n"; ++ return ExitConditions::CLI_FAILURE; ++ } + } else { + CoffReaderWriter coff_reader(report_args.at("coff")); + CoffParser coff(&coff_reader); +diff --git a/src/utils.cxx b/src/utils.cxx +index a22dcd5..2ed3bdb 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -20,6 +21,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -484,6 +486,12 @@ void replace_path_characters(char* path, size_t len) { + * \param bsize the lengh of the padding to add + */ + char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++ // If str_size > bsize we get inappropriate conversion ++ // from signed to unsigned ++ if (str_size > bsize) { ++ debug("Padding string is greater than max string size allowed"); ++ return nullptr; ++ } + size_t const extended_buf = bsize - str_size + 2; + char* padded_path = new char[bsize + 1]; + for (DWORD i = 0, j = 0; i < bsize && j < str_size; ++i) { +@@ -515,17 +523,16 @@ int get_padding_length(const std::string& name) { + return count; + } + +-std::string strip_padding(const std::string &lib) +-{ ++std::string strip_padding(const std::string& lib) { + // One of the padding characters is a legitimate + // path separator +- int pad_len = get_padding_length(lib)-1; ++ int const pad_len = get_padding_length(lib) - 1; + // Capture the drive and drive separator +- std::string::const_iterator p = lib.cbegin(); +- std::string::const_iterator e = lib.cbegin()+2; +- std::string stripped_drive(p, e); ++ std::string::const_iterator const p = lib.cbegin(); ++ std::string::const_iterator e = lib.cbegin() + 2; ++ std::string const stripped_drive(p, e); + e = e + pad_len; +- std::string path_remainder(e, lib.end()); ++ std::string const path_remainder(e, lib.end()); + return stripped_drive + path_remainder; + } + +@@ -545,6 +552,33 @@ std::string mangle_name(const std::string& name) { + // relative paths, assume they're relative to the CWD of the linker (as they have to be) + abs_out = join({GetCWD(), name}, "\\"); + } ++ // Now that we have the full path, check size ++ if (abs_out.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ // strip prefix ++ std::string const pre = getenv("SPACK_STAGE_DIR"); ++ std::string const rel = R"(\\?\)" + lstrip(abs_out, pre); ++ // Get SFN length so we can create buffer ++ DWORD const sfn_size = GetShortPathNameA(rel.c_str(), nullptr, 0); ++ char* sfn = new char[sfn_size + 1]; ++ GetShortPathNameA(rel.c_str(), sfn, rel.length()); ++ // sfn is null terminated per win32 api ++ std::string const rel_sfn = std::string(sfn); ++ std::string const new_abs_out = join({pre, rel_sfn}, "\\"); ++ // If new, shortened path is too long, bail ++ if (new_abs_out.length() > MAX_NAME_LEN) { ++ std::cerr << "DLL path " << abs_out << " too long to relocate.\n"; ++ std::cerr << "Shortened DLL path " << new_abs_out ++ << " also too long to relocate.\n"; ++ std::cerr << "Please move prefix " << pre ++ << " to a shorter directory.\n"; ++ delete[] sfn; ++ throw SpackCompilerWrapperError( ++ "DLL Path too long, cannot be relocated."); ++ } ++ delete[] sfn; ++ abs_out = new_abs_out; ++ } + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -579,7 +613,7 @@ bool SpackInstalledLib(const std::string& lib) { + "unset"); + return false; + } +- std::string stripped_lib = strip_padding(lib); ++ std::string const stripped_lib = strip_padding(lib); + startswith(stripped_lib, prefix); + } + +@@ -732,3 +766,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { + } + return nullptr; + } ++ ++SpackCompilerWrapperError::SpackCompilerWrapperError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* SpackCompilerWrapperError::what() const { ++ return exception::what(); ++} +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index 83e0358..b692e38 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -172,7 +172,7 @@ void replace_path_characters(char* path, size_t len); + + void replace_special_characters(char* mangled, size_t len); + +-bool SpackInstalledLib(const std::string &lib); ++bool SpackInstalledLib(const std::string& lib); + + // File and File handle helpers // + +@@ -237,4 +237,10 @@ const std::map path_to_special_characters{{'\\', '|'}, + {'/', '|'}, + {':', ';'}}; + ++class SpackCompilerWrapperError : public std::runtime_error { ++ public: ++ SpackCompilerWrapperError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index 387b245..5671515 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,7 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include ++#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -102,7 +102,9 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { + char* new_lib_pth = + pad_path(new_library_loc.c_str(), + static_cast(new_library_loc.size())); +- ++ if (!new_lib_pth) { ++ return false; ++ } + replace_special_characters(new_lib_pth, MAX_NAME_LEN); + + // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +@@ -404,11 +406,18 @@ bool LibRename::ExecuteLibRename() { + std::cerr << err; + return false; + } +- std::string mangled_name = mangle_name(this->pe); +- if (!coff_parser.NormalizeName(mangled_name)) { +- std::cerr << "Unable to normalize name: " << mangled_name << "\n"; ++ try { ++ std::string mangled_name = mangle_name(this->pe); ++ if (!coff_parser.NormalizeName(mangled_name)) { ++ std::cerr << "Unable to normalize name: " << mangled_name << "\n"; ++ return false; ++ } ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Unable to mangle name, DLL name " << this->pe ++ << " too long\n"; + return false; + } ++ + return true; + } + From 5e4ad421c7eb5e33887f334a81cbce2c3e42492b Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 10 Sep 2025 10:20:02 -0400 Subject: [PATCH 11/81] Set spack stage in env Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index a0998919bd6..b1ea4f0cd58 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -60,7 +60,7 @@ class CompilerWrapper(Package, NMakePackage): version("0.1.0", sha256="4eab2cb48bb83edb88780517c9dfa55778f8adc555ec4939cb73e2d05fed5a5a") with when("@develop platform=windows"): - patch("quoting.patch") + # patch("quoting.patch") patch("long_name.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): @@ -213,6 +213,8 @@ def setup_dependent_build_environment( for item in env_paths: env.prepend_path("SPACK_COMPILER_WRAPPER_PATH", item) + env.set("SPACK_STAGE_DIR", self.pkg.stage.source_path) + class GenericBuilder(GenericBuilder, EnvironmentSetup): From 42d74e8f18e85f5e990c62edd1cd1253e7c1fdbb Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 10 Sep 2025 10:20:09 -0400 Subject: [PATCH 12/81] add long name patch Signed-off-by: John Parent wip Signed-off-by: John Parent --- .../packages/compiler_wrapper/long_name.patch | 13 +- .../compiler_wrapper/long_path_support.patch | 475 ++++++++++++++++++ .../packages/compiler_wrapper/package.py | 4 +- 3 files changed, 485 insertions(+), 7 deletions(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch index 4193b613db2..d533fe9de68 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch @@ -78,7 +78,7 @@ index 9458d17..26d940d 100644 CoffReaderWriter coff_reader(report_args.at("coff")); CoffParser coff(&coff_reader); diff --git a/src/utils.cxx b/src/utils.cxx -index a22dcd5..2ed3bdb 100644 +index a22dcd5..5258caa 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -11,6 +11,7 @@ @@ -134,7 +134,7 @@ index a22dcd5..2ed3bdb 100644 return stripped_drive + path_remainder; } -@@ -545,6 +552,33 @@ std::string mangle_name(const std::string& name) { +@@ -545,6 +552,36 @@ std::string mangle_name(const std::string& name) { // relative paths, assume they're relative to the CWD of the linker (as they have to be) abs_out = join({GetCWD(), name}, "\\"); } @@ -142,7 +142,10 @@ index a22dcd5..2ed3bdb 100644 + if (abs_out.length() > MAX_NAME_LEN) { + // Name is too long we need to attempt to shorten + // strip prefix -+ std::string const pre = getenv("SPACK_STAGE_DIR"); ++ std::string pre = GetSpackEnv("SPACK_STAGE_DIR"); ++ if (pre.empty()) { ++ pre = GetCWD(); ++ } + std::string const rel = R"(\\?\)" + lstrip(abs_out, pre); + // Get SFN length so we can create buffer + DWORD const sfn_size = GetShortPathNameA(rel.c_str(), nullptr, 0); @@ -168,7 +171,7 @@ index a22dcd5..2ed3bdb 100644 char* chr_abs_out = new char[abs_out.length() + 1]; strcpy(chr_abs_out, abs_out.c_str()); replace_path_characters(chr_abs_out, abs_out.length()); -@@ -579,7 +613,7 @@ bool SpackInstalledLib(const std::string& lib) { +@@ -579,7 +616,7 @@ bool SpackInstalledLib(const std::string& lib) { "unset"); return false; } @@ -177,7 +180,7 @@ index a22dcd5..2ed3bdb 100644 startswith(stripped_lib, prefix); } -@@ -732,3 +766,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { +@@ -732,3 +769,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { } return nullptr; } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch new file mode 100644 index 00000000000..775b9f78310 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch @@ -0,0 +1,475 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index cbc9f76..9979ea1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -26,6 +26,9 @@ jobs: + - name: Remove Git Bash tools + run: | + rmdir /s /q "C:\Program Files\Git\usr" ++ - name: enable 8.3 names ++ run: | ++ fsutil 8dot3name set 0 + - name: "Test RPath" + run: | + test\setup_and_drive_test.bat +diff --git a/Makefile b/Makefile +index 6a0f625..a86ace7 100644 +--- a/Makefile ++++ b/Makefile +@@ -141,10 +141,41 @@ test_zerowrite: build_zerowrite_test + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_long_paths: build_and_check_test_sample ++ mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ xcopy test\main.cxx tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ rename calc.cxx verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.cxx ++ copy ..\..\..\..\cl.exe cl.exe ++ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe ++ cl /c /EHsc "verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include ++ cl /c /EHsc main.cxx /I include ++ link $(LFLAGS) verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.obj /DLL ++ link $(LFLAGS) main.obj verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe ++ tester.exe ++ cd ../../../.. ++ ++test_relocate_long_paths: test_long_paths ++ cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname ++ -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe ++ cd .. ++ mkdir tmp_bin ++ mkdir tmp_lib ++ move evenlongersubdirectoryname\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll ++ move evenlongersubdirectoryname\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ cd evenlongersubdirectoryname ++ del tester.exe ++ link main.obj ..\tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe ++ .\tester.exe ++ cd ../../../.. ++ + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_long_paths test_pipe_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index f6b273c..3cda566 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -378,7 +378,8 @@ bool CoffParser::NormalizeName(std::string& name) { + // The dll is found with and without an extenion, depending on the context of the location + // i.e. in the section data, it can be found with both an extension and extensionless + // whereas in the symbol table or linker member strings, it's always found without an extension +- std::string const name_no_ext = strip(name, ".dll"); ++ std::string const name_no_dll = strip(name, ".dll"); ++ std::string const name_no_ext = strip(name_no_dll, ".DLL"); + // Flag allowing us to skip multiple attempts + // to rename the long names member this name + bool long_name_renamed = false; +diff --git a/src/ld.cxx b/src/ld.cxx +index ff9bdde..890f364 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,7 @@ + #include "ld.h" + #include + #include ++#include + #include "coff_parser.h" + #include "coff_reader_writer.h" + #include "linker_invocation.h" +@@ -34,7 +35,14 @@ DWORD LdInvocation::InvokeToolchain() { + // We're creating a dll, we need to create an appropriate import lib + if (!link_run.IsExeLink()) { + std::string const imp_lib_name = link_run.get_implib_name(); +- std::string dll_name = link_run.get_mangled_out(); ++ std::string dll_name; ++ try { ++ dll_name = link_run.get_mangled_out(); ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Unable to mangle PE " << link_run.get_out() ++ << " name is too long\n"; ++ return ExitConditions::NORMALIZE_NAME_FAILURE; ++ } + std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; + std::string def = "-def "; + std::string piped_args = link_run.get_lib_link_args(); +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 5e7b60b..fc6316f 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -50,7 +50,10 @@ void LinkerInvocation::Parse() { + this->is_exe_ = false; + } else if (startswith(normal_token, "-out") || + startswith(normal_token, "/out")) { +- this->output_ = split(*token, ":")[1]; ++ StrList split_out = split(*token, ":", 1); ++ StrList const split_name = ++ StrList(split_out.begin() + 1, split_out.end()); ++ this->output_ = join(split_name, "\\"); + } else if (endswith(normal_token, ".obj")) { + this->objs_.push_back(*token); + } else if (startswith(normal_token, "@") && +@@ -67,7 +70,11 @@ void LinkerInvocation::Parse() { + } + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; + if (this->output_.empty()) { +- this->output_ = strip(this->objs_.front(), ".obj") + ext; ++ // with no "out" argument, the linker ++ // will place the file in the CWD ++ std::string const name_obj = this->objs_.front(); ++ std::string const filename = split(name_obj, "\\").back(); ++ this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; + } + this->name_ = strip(this->output_, ext); + if (this->implibname_.empty()) { +diff --git a/src/main.cxx b/src/main.cxx +index 9458d17..26d940d 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -89,12 +89,19 @@ int main(int argc, const char* argv[]) { + } + DEBUG = debug; + std::unique_ptr rpath_lib; +- if (has_coff) { +- rpath_lib = std::make_unique( +- patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); +- } else { +- rpath_lib = std::make_unique(patch_args.at("pe"), full, +- deploy, true); ++ try { ++ if (has_coff) { ++ rpath_lib = std::make_unique(patch_args.at("pe"), ++ patch_args.at("coff"), ++ full, deploy, true); ++ } else { ++ rpath_lib = std::make_unique(patch_args.at("pe"), ++ full, deploy, true); ++ } ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Cannot Rename PE file " << patch_args.at("pe") ++ << " it contains references that are too long.\n"; ++ return ExitConditions::RENAME_FAILURE; + } + if (!rpath_lib->ExecuteRename()) { + std::cerr << "Library rename failed\n"; +@@ -110,9 +117,17 @@ int main(int argc, const char* argv[]) { + return ExitConditions::CLI_FAILURE; + } + if (report_args.find("pe") != report_args.end()) { +- LibRename portable_executable(report_args.at("pe"), std::string(), +- false, false, true); +- portable_executable.ExecuteRename(); ++ try { ++ LibRename portable_executable( ++ report_args.at("pe"), std::string(), false, false, true); ++ portable_executable.ExecuteRename(); ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr ++ << "Unable to parse command line for reporting\n" ++ << "run command with --help flag for accepted command " ++ "line arguments\n"; ++ return ExitConditions::CLI_FAILURE; ++ } + } else { + CoffReaderWriter coff_reader(report_args.at("coff")); + CoffParser coff(&coff_reader); +diff --git a/src/utils.cxx b/src/utils.cxx +index a22dcd5..eb1ce9c 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -11,15 +11,18 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -122,16 +125,25 @@ std::wstring ConvertASCIIToWide(const std::string& str) { + * Decomposes the input string into a list separated by + * delim + * ++ * Count determines how many delims will be processed ++ * if count > 0 ++ * + * Returns the list produced by breaking up input string s on delim + */ +-StrList split(const std::string& str, const std::string& delim) { ++StrList split(const std::string& str, const std::string& delim, ++ const u_int count) { + size_t pos_start = 0; + size_t pos_end; + size_t const delim_len = delim.length(); + std::string token; + StrList res = StrList(); +- +- while ((pos_end = str.find(delim, pos_start)) != std::string::npos) { ++ u_int delim_count = count; ++ if (!count) { ++ delim_count += 1; ++ } ++ u_int delim_found = 0; ++ while (((pos_end = str.find(delim, pos_start)) != std::string::npos) && ++ delim_found < delim_count) { + size_t const token_len = pos_end - pos_start; + token = str.substr(pos_start, token_len); + pos_start = pos_end + delim_len; +@@ -139,6 +151,9 @@ StrList split(const std::string& str, const std::string& delim) { + continue; + } + res.push_back(token); ++ if (count) { ++ delim_found += 1; ++ } + } + res.push_back(str.substr(pos_start)); + return res; +@@ -163,7 +178,7 @@ std::string strip(const std::string& str, const std::string& substr) { + std::string lstrip(const std::string& str, const std::string& substr) { + if (!startswith(str, substr)) + return str; +- return str.substr(substr.size() - 1, str.size()); ++ return str.substr(substr.size(), str.size()); + } + + /** +@@ -484,6 +499,12 @@ void replace_path_characters(char* path, size_t len) { + * \param bsize the lengh of the padding to add + */ + char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++ // If str_size > bsize we get inappropriate conversion ++ // from signed to unsigned ++ if (str_size > bsize) { ++ debug("Padding string is greater than max string size allowed"); ++ return nullptr; ++ } + size_t const extended_buf = bsize - str_size + 2; + char* padded_path = new char[bsize + 1]; + for (DWORD i = 0, j = 0; i < bsize && j < str_size; ++i) { +@@ -515,20 +536,65 @@ int get_padding_length(const std::string& name) { + return count; + } + +-std::string strip_padding(const std::string &lib) +-{ ++std::string strip_padding(const std::string& lib) { + // One of the padding characters is a legitimate + // path separator +- int pad_len = get_padding_length(lib)-1; ++ int const pad_len = get_padding_length(lib) - 1; + // Capture the drive and drive separator +- std::string::const_iterator p = lib.cbegin(); +- std::string::const_iterator e = lib.cbegin()+2; +- std::string stripped_drive(p, e); ++ std::string::const_iterator const p = lib.cbegin(); ++ std::string::const_iterator e = lib.cbegin() + 2; ++ std::string const stripped_drive(p, e); + e = e + pad_len; +- std::string path_remainder(e, lib.end()); ++ std::string const path_remainder(e, lib.end()); + return stripped_drive + path_remainder; + } + ++std::string getSFN(const std::string& path) { ++ std::string const escaped = R"(\\?\)" + path; ++ // Get SFN length so we can create buffer ++ DWORD const sfn_size = ++ GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT ++ char* sfn = new char[sfn_size + 1]; ++ GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ // sfn is null terminated per win32 api ++ std::string s_sfn = std::string(sfn); ++ delete[] sfn; ++ return s_sfn; ++} ++ ++/** ++ * Replace path after a given prefix with the SFN representation ++ */ ++std::string short_name_post_prefix(const std::string& path) { ++ // strip prefix ++ std::string pre = GetSpackEnv("SPACK_CONTEXT_ROOT"); ++ if (pre.empty()) { ++ pre = GetCWD(); ++ } ++ // Get SFN for path to name ++ // Use "disable string parsing" prefix in case ++ // the path is too long ++ std::string const abs_sfn = getSFN(path); ++ // Get SFN for path to name prefix ++ std::string const pre_sfn = getSFN(pre); ++ // Strip prefix SFN so we can prepend the real prefix to it ++ // Prefix should be spack stage root, which allows us the maximal ++ // possible path shortening without effecting potential naming collisions ++ // and still allowing us to determine we're in a spack context ++ std::string const rel_sfn = lstrip(abs_sfn, pre_sfn); ++ std::string new_abs_out = pre + rel_sfn; ++ if (new_abs_out.length() > MAX_NAME_LEN) { ++ std::cerr << "DLL path " << path << " too long to relocate.\n"; ++ std::cerr << "Shortened DLL path " << new_abs_out ++ << " also too long to relocate.\n"; ++ std::cerr << "Please move Spack prefix " ++ << " to a shorter directory.\n"; ++ throw SpackCompilerWrapperError( ++ "DLL Path too long, cannot be relocated."); ++ } ++ return new_abs_out; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -545,6 +611,13 @@ std::string mangle_name(const std::string& name) { + // relative paths, assume they're relative to the CWD of the linker (as they have to be) + abs_out = join({GetCWD(), name}, "\\"); + } ++ // Now that we have the full path, check size ++ if (abs_out.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const new_abs_out = short_name_post_prefix(abs_out); ++ // If new, shortened path is too long, bail ++ abs_out = new_abs_out; ++ } + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -579,7 +652,7 @@ bool SpackInstalledLib(const std::string& lib) { + "unset"); + return false; + } +- std::string stripped_lib = strip_padding(lib); ++ std::string const stripped_lib = strip_padding(lib); + startswith(stripped_lib, prefix); + } + +@@ -732,3 +805,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { + } + return nullptr; + } ++ ++SpackCompilerWrapperError::SpackCompilerWrapperError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* SpackCompilerWrapperError::what() const { ++ return exception::what(); ++} +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index 83e0358..eaa691b 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -71,7 +71,8 @@ std::wstring ConvertASCIIToWide(const std::string& str); + // Splits argument "s" by delineator delim + // Returns vector of strings, if delim is present + // Returns a single item list +-StrList split(const std::string& s, const std::string& delim); ++StrList split(const std::string& s, const std::string& delim, ++ const u_int count = 0); + + //Strips substr off the RHS of the larger string + std::string strip(const std::string& s, const std::string& substr); +@@ -162,6 +163,8 @@ bool IsPathAbsolute(const std::string& pth); + + bool hasPathCharacters(const std::string& name); + ++std::string short_name_post_prefix(const std::string& path); ++ + std::string mangle_name(const std::string& name); + + int get_padding_length(const std::string& name); +@@ -172,7 +175,7 @@ void replace_path_characters(char* path, size_t len); + + void replace_special_characters(char* mangled, size_t len); + +-bool SpackInstalledLib(const std::string &lib); ++bool SpackInstalledLib(const std::string& lib); + + // File and File handle helpers // + +@@ -237,4 +240,10 @@ const std::map path_to_special_characters{{'\\', '|'}, + {'/', '|'}, + {':', ';'}}; + ++class SpackCompilerWrapperError : public std::runtime_error { ++ public: ++ SpackCompilerWrapperError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index 387b245..fd3c163 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -92,17 +92,28 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { + return false; + } + LibraryFinder lib_finder; +- std::string const new_library_loc = ++ std::string new_library_loc = + lib_finder.FindLibrary(file_name, dll_path); + if (new_library_loc.empty()) { + std::cerr << "Unable to find library " << file_name << " from " + << dll_path << " for relocation" << "\n"; + return false; + } ++ if (new_library_loc.length() > MAX_NAME_LEN) { ++ try { ++ std::string const short_lib_loc = ++ short_name_post_prefix(new_library_loc); ++ new_library_loc = short_lib_loc; ++ } catch (SpackCompilerWrapperError& e) { ++ return false; ++ } ++ } + char* new_lib_pth = + pad_path(new_library_loc.c_str(), + static_cast(new_library_loc.size())); +- ++ if (!new_lib_pth) { ++ return false; ++ } + replace_special_characters(new_lib_pth, MAX_NAME_LEN); + + // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +@@ -404,11 +415,18 @@ bool LibRename::ExecuteLibRename() { + std::cerr << err; + return false; + } +- std::string mangled_name = mangle_name(this->pe); +- if (!coff_parser.NormalizeName(mangled_name)) { +- std::cerr << "Unable to normalize name: " << mangled_name << "\n"; ++ try { ++ std::string mangled_name = mangle_name(this->pe); ++ if (!coff_parser.NormalizeName(mangled_name)) { ++ std::cerr << "Unable to normalize name: " << mangled_name << "\n"; ++ return false; ++ } ++ } catch (const SpackCompilerWrapperError& e) { ++ std::cerr << "Unable to mangle name, DLL name " << this->pe ++ << " too long\n"; + return false; + } ++ + return true; + } + diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index b1ea4f0cd58..8747756a1f5 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -61,7 +61,7 @@ class CompilerWrapper(Package, NMakePackage): with when("@develop platform=windows"): # patch("quoting.patch") - patch("long_name.patch") + patch("long_path_support.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): patch("fixup11.patch") @@ -213,7 +213,7 @@ def setup_dependent_build_environment( for item in env_paths: env.prepend_path("SPACK_COMPILER_WRAPPER_PATH", item) - env.set("SPACK_STAGE_DIR", self.pkg.stage.source_path) + env.set("SPACK_CONTEXT_ROOT", dependent_spec.package.stage.source_path) class GenericBuilder(GenericBuilder, EnvironmentSetup): From 6a62bf46bef269af08d78288090899a1ce158ac8 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 19 Sep 2025 17:07:24 -0400 Subject: [PATCH 13/81] compielr-wrapper: add c_cxx patch Handles cases where either c or cxx is not required Signed-off-by: John Parent --- .../packages/compiler_wrapper/c_cxx.patch | 16 ++++++++++++++++ .../builtin/packages/compiler_wrapper/package.py | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch new file mode 100644 index 00000000000..aeac5374a24 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch @@ -0,0 +1,16 @@ +diff --git a/src/cl.cxx b/src/cl.cxx +index 33fbedf..88c8c61 100644 +--- a/src/cl.cxx ++++ b/src/cl.cxx +@@ -8,5 +8,10 @@ + #include "spack_env.h" + + void ClInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { +- this->command = spackenv.SpackCC; ++ // C and CXX compiler executables are the same for MSVC (both cl, hence the class name) ++ // However, depending on how a package depends on the languages, one or both ++ // may be present in the env. Arbitrarily prefer the C compiler as the choice ++ // truly doesn't matter ++ this->command = ++ !spackenv.SpackCC.empty() ? spackenv.SpackCC : spackenv.SpackCXX; + } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 8747756a1f5..bf05ebeffbb 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -61,7 +61,8 @@ class CompilerWrapper(Package, NMakePackage): with when("@develop platform=windows"): # patch("quoting.patch") - patch("long_path_support.patch") + # patch("long_path_support.patch") + patch("c_cxx.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): patch("fixup11.patch") From 66aff8f3c7eefecf77b541018ef230273c582965 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 30 Sep 2025 12:01:14 -0400 Subject: [PATCH 14/81] def handling pass Signed-off-by: John Parent --- .../improve_def_forwarding.patch | 78 +++++++++++++++++++ .../packages/compiler_wrapper/package.py | 1 + 2 files changed, 79 insertions(+) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch new file mode 100644 index 00000000000..cc6a97639ca --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch @@ -0,0 +1,78 @@ +diff --git a/src/ld.cxx b/src/ld.cxx +index 8669273..073f7d0 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -44,8 +44,12 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; +- std::string def = "-def "; +- std::string piped_args = link_run.get_lib_link_args(); ++ std::string const def_file = link_run.get_def_file().empty() ++ ? " " ++ : ": " + link_run.get_def_file(); ++ std::string const def = "-def" + def_file; ++ ++ std::string const piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib + this->rpath_executor = + ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 1a96b5a..9bd326f 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -46,11 +46,10 @@ void LinkerInvocation::Parse() { + this->implibname_ = implib_line[1]; + } else if (endswith(normal_token, ".lib")) { + this->libs_.push_back(*token); +- } else if (normal_token == "/dll" || normal_token == "-dll") { ++ } else if (normal_token == "dll") { + this->is_exe_ = false; +- } else if (startswith(normal_token, "-out") || +- startswith(normal_token, "/out")) { +- this->output_ = split(*token, ":", 1)[1]; ++ } else if (startswith(normal_token, "out")) { ++ this->output_ = split(*token, ":")[1]; + } else if (endswith(normal_token, ".obj")) { + this->objs_.push_back(*token); + } else if (startswith(normal_token, "@") && +@@ -60,6 +59,8 @@ void LinkerInvocation::Parse() { + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits + this->rsp_file_ = *token; ++ } else if (startswith(normal_token, "def")) { ++ this->def_file_ = strip(split(*token, ":")[1], "\""); + } else if (this->piped_args_.find(normal_token) != + this->piped_args_.end()) { + this->piped_args_.at(normal_token).emplace_back(normal_token); +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index d195afe..a892094 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -36,9 +36,8 @@ class LinkerInvocation { + StrList objs_; + bool is_exe_; + std::map piped_args_ = { +- {"def", {}}, {"export", {}}, {"include", {}}, +- {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, +- {"nodefaultlib", {}}, {"subsystem", {}}, {"verbose", {}}, +- {"wx", {}}, ++ {"export", {}}, {"include", {}}, {"libpath", {}}, ++ {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, ++ {"subsystem", {}}, {"verbose", {}}, {"wx", {}}, + }; + }; +\ No newline at end of file +diff --git a/src/utils.cxx b/src/utils.cxx +index 49db7f4..df5822a 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -449,7 +449,7 @@ void normalArg(std::string& arg) { + // first normalize capitalization + lower(arg); + // strip leading / and - +- arg = strip(strip(arg, "-"), "/"); ++ arg = lstrip(lstrip(arg, "-"), "/"); + } + + std::string reportLastError() { diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index bf05ebeffbb..fae90901385 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -63,6 +63,7 @@ class CompilerWrapper(Package, NMakePackage): # patch("quoting.patch") # patch("long_path_support.patch") patch("c_cxx.patch") + patch("improve_def_forwarding.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): patch("fixup11.patch") From 8a2b42670740b03f73d82ea493d0093fc0f23ce3 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 30 Sep 2025 19:12:30 -0400 Subject: [PATCH 15/81] Better def forwarding patch Signed-off-by: John Parent --- ...patch => improve_def_arg_forwarding.patch} | 51 ++++++++++++++++--- .../packages/compiler_wrapper/package.py | 4 +- 2 files changed, 44 insertions(+), 11 deletions(-) rename repos/spack_repo/builtin/packages/compiler_wrapper/{improve_def_forwarding.patch => improve_def_arg_forwarding.patch} (64%) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch similarity index 64% rename from repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch rename to repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch index cc6a97639ca..1889d490897 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_forwarding.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch @@ -1,5 +1,5 @@ diff --git a/src/ld.cxx b/src/ld.cxx -index 8669273..073f7d0 100644 +index 8669273..7bdb209 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -44,8 +44,12 @@ DWORD LdInvocation::InvokeToolchain() { @@ -10,7 +10,7 @@ index 8669273..073f7d0 100644 - std::string piped_args = link_run.get_lib_link_args(); + std::string const def_file = link_run.get_def_file().empty() + ? " " -+ : ": " + link_run.get_def_file(); ++ : ":" + link_run.get_def_file(); + std::string const def = "-def" + def_file; + + std::string const piped_args = link_run.get_lib_link_args(); @@ -18,7 +18,7 @@ index 8669273..073f7d0 100644 this->rpath_executor = ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 1a96b5a..9bd326f 100644 +index 1a96b5a..dd26c30 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -46,11 +46,10 @@ void LinkerInvocation::Parse() { @@ -36,15 +36,19 @@ index 1a96b5a..9bd326f 100644 } else if (endswith(normal_token, ".obj")) { this->objs_.push_back(*token); } else if (startswith(normal_token, "@") && -@@ -60,6 +59,8 @@ void LinkerInvocation::Parse() { +@@ -60,9 +59,11 @@ void LinkerInvocation::Parse() { // Primarily utilized by CMake and MSBuild projects to bypass // Command line length limits this->rsp_file_ = *token; + } else if (startswith(normal_token, "def")) { -+ this->def_file_ = strip(split(*token, ":")[1], "\""); ++ this->def_file_ = strip(split(*token, ":", 1)[1], "\""); } else if (this->piped_args_.find(normal_token) != this->piped_args_.end()) { - this->piped_args_.at(normal_token).emplace_back(normal_token); +- this->piped_args_.at(normal_token).emplace_back(normal_token); ++ this->piped_args_.at(normal_token).emplace_back(*token); + } + } + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; diff --git a/src/linker_invocation.h b/src/linker_invocation.h index d195afe..a892094 100644 --- a/src/linker_invocation.h @@ -64,15 +68,46 @@ index d195afe..a892094 100644 }; \ No newline at end of file diff --git a/src/utils.cxx b/src/utils.cxx -index 49db7f4..df5822a 100644 +index 49db7f4..f2a98e1 100644 --- a/src/utils.cxx +++ b/src/utils.cxx -@@ -449,7 +449,7 @@ void normalArg(std::string& arg) { +@@ -224,6 +224,14 @@ void lower(std::string& str) { + }); + } + ++/** ++ * Quotes str as needed ++ * If str has existing escaped quotes, or a space/reserved character ++ * Escape escaped quotes using an escaped backslash preceding the escaped ++ * quote. Escape reserved characters by quoting the entire string ++ * ++ * Return the escaped string ++ */ + std::string quoteAsNeeded(std::string& str) { + // Note: the ordering if these two conditionals is important + // If the second conditional is executed first, the first +@@ -448,8 +456,10 @@ bool isCommandArg(const std::string& arg, const std::string& command) { + void normalArg(std::string& arg) { // first normalize capitalization lower(arg); ++ // strip any leading/trailing quotes ++ arg = strip(lstrip(arg, "\""), "\""); // strip leading / and - - arg = strip(strip(arg, "-"), "/"); + arg = lstrip(lstrip(arg, "-"), "/"); } std::string reportLastError() { +diff --git a/src/utils.h b/src/utils.h +index e2f5a80..e5a7ed3 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -132,6 +132,8 @@ char* findstr(char* search_str, const char* substr, size_t size); + // side effects on Windows + void quoteList(StrList& args); + ++std::string quoteAsNeeded(std::string& str); ++ + /// @brief Searches a sections of a string for a given regex using provided + /// options to control search behavior + /// @param searchDomain - string to be searched diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index fae90901385..bd85ff9d415 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -60,10 +60,8 @@ class CompilerWrapper(Package, NMakePackage): version("0.1.0", sha256="4eab2cb48bb83edb88780517c9dfa55778f8adc555ec4939cb73e2d05fed5a5a") with when("@develop platform=windows"): - # patch("quoting.patch") - # patch("long_path_support.patch") patch("c_cxx.patch") - patch("improve_def_forwarding.patch") + patch("improve_def_arg_forwarding.patch") # available in 0.1.1 with when("@0.1.0 platform=windows"): patch("fixup11.patch") From a6b8450970951a9879b0a6161ee99fa01bf0a82a Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 31 Oct 2025 16:41:01 -0400 Subject: [PATCH 16/81] Update wrapper pkg Signed-off-by: John Parent --- .../def-and-exe-enhanced-support.patch | 915 ++++++++++++++++++ .../packages/compiler_wrapper/package.py | 15 +- 2 files changed, 916 insertions(+), 14 deletions(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch new file mode 100644 index 00000000000..7148b26e77e --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch @@ -0,0 +1,915 @@ +diff --git a/Makefile b/Makefile +index e31f957..45f668f 100644 +--- a/Makefile ++++ b/Makefile +@@ -66,6 +66,9 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe ++ echo "-------------------" ++ echo "Running Test Setup" ++ echo "-------------------" + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -77,6 +80,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test ++ echo "--------------------" ++ echo "Building Test Sample" ++ echo "--------------------" + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -88,6 +94,9 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample ++ echo "--------------------" ++ echo "Running Wrapper Test" ++ echo "--------------------" + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -101,6 +110,9 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample ++ echo "--------------------------" ++ echo "Running Relocate Exe Test" ++ echo "--------------------------" + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +@@ -112,6 +124,9 @@ test_relocate_exe: build_and_check_test_sample + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample ++ echo "--------------------------" ++ echo "Running Relocate DLL test" ++ echo "--------------------------" + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -127,6 +142,9 @@ test_relocate_dll: build_and_check_test_sample + cd ../.. + + test_pipe_overflow: build_and_check_test_sample ++ echo "--------------------" ++ echo " Pipe overflow test" ++ echo "--------------------" + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" +@@ -136,12 +154,18 @@ build_zerowrite_test: test\writezero.obj + link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe + + test_zerowrite: build_zerowrite_test ++ echo "-----------------------" ++ echo "Running zerowrite test" ++ echo "-----------------------" + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample ++ echo "------------------------" ++ echo "Running long paths test" ++ echo "------------------------" + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -158,6 +182,9 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths ++ echo "---------------------------------" ++ echo "Running relocate logn paths test" ++ echo "---------------------------------" + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -172,10 +199,49 @@ test_relocate_long_paths: test_long_paths + .\tester.exe + cd ../../../.. + ++test_def_file_name_override: ++ mkdir tmp\test\def\def_override ++ xcopy /E test\include tmp\test\def\def_override ++ xcopy /E "test\src file" tmp\test\def\def_override ++ xcopy test\main.cxx tmp\test\def\def_override ++ xcopy test\calc.def tmp\test\def\def_override ++ cd tmp\test\def\def_override ++ copy ..\..\..\..\cl.exe cl.exe ++ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe ++ cl /c /EHsc "calc.cxx" /DCALC_DEF_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include ++ cl /c /EHsc main.cxx /I include ++ link $(LFLAGS) /DEF:calc.def calc.obj /DLL ++ link $(LFLAGS) main.obj calc.lib /out:tester.exe ++ tester.exe ++ cd ../../../.. ++ ++test_exe_with_exports: ++ echo ------------------------------ ++ echo Running exe with exports test ++ echo ------------------------------ ++ mkdir tmp\test\exe_with_exports ++ xcopy /E test\include tmp\test\exe_with_exports ++ xcopy /E "test\src file" tmp\test\exe_with_exports ++ xcopy test\main2.h tmp\test\exe_with_exports ++ xcopy test\main2.cxx tmp\test\exe_with_exports ++ xcopy test\main3.cxx tmp\test\exe_with_exports ++ cd tmp\test\exe_with_exports ++ copy ..\..\..\cl.exe cl.exe ++ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe ++ cl /c /EHsc "calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include ++ cl /c /EHsc main2.cxx /DMAIN_EXPORTS /I include ++ cl /c /EHsc main3.cxx /I include ++ link $(LFLAGS) calc.obj /out:calc.dll /DLL ++ link $(LFLAGS) main2.obj calc.lib /out:tester1.exe ++ link $(LFLAGS) main3.obj calc.lib tester1.lib /out:tester2.exe ++ tester1.exe ++ tester2.exe ++ cd ../../.. ++ + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_exe_with_exports test_def_file_name_override test_long_paths test_pipe_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 3cda566..25f4b28 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -292,7 +292,7 @@ bool CoffParser::ValidateLongName(coff_member* member, int size) { + } + // If a name has an object file, this is not an import + // member +- char* obj_res = findstr(member->data, ".obj", size); ++ char const* obj_res = findstr(member->data, ".obj", size); + return obj_res == nullptr; + } + +@@ -326,7 +326,7 @@ void CoffParser::NormalizeSectionNames(const std::string& name, char* section, + size_t data_size) { + size_t const name_len = name.size(); + char* section_search_start = section; +- char* search_terminator = section + data_size; ++ char const* search_terminator = section + data_size; + ptrdiff_t offset = 0; + while (section_search_start && (section_search_start < search_terminator)) { + // findstr's final parameter takes the size of the search domain +@@ -378,8 +378,7 @@ bool CoffParser::NormalizeName(std::string& name) { + // The dll is found with and without an extenion, depending on the context of the location + // i.e. in the section data, it can be found with both an extension and extensionless + // whereas in the symbol table or linker member strings, it's always found without an extension +- std::string const name_no_dll = strip(name, ".dll"); +- std::string const name_no_ext = strip(name_no_dll, ".DLL"); ++ std::string const name_no_ext = stripLastExt(name); + // Flag allowing us to skip multiple attempts + // to rename the long names member this name + bool long_name_renamed = false; +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 76b95af..38bd760 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -28,20 +28,6 @@ int redefinedArgCheck(const std::map& args, + return 0; + } + +-/** +- * Check for the presense of an argument in the argument map +- */ +-int checkArgumentPresence(const std::map& args, +- const char* val, bool required = true) { +- if (args.find(val) == args.end()) { +- std::cerr << "Warning! Argument (" << val << ") not present\n"; +- if (required) { +- return 0; +- } +- } +- return 1; +-} +- + bool print_help() { + std::cout << "Spack's Windows compiler wrapper\n"; + std::cout << "Version: " << STRING(MSVC_WRAPPER_VERSION) << "\n"; +@@ -82,28 +68,20 @@ bool print_help() { + "this file:\n"; + std::cout << "\n"; + std::cout << " Options:\n"; +- std::cout << " --pe = PE " ++ std::cout << " --pe = PE " + "(dll/exe) file to be relocated\n"; +- std::cout << " [--coff ] = " ++ std::cout << " --coff = " + "COFF (import library) file to be relocated\n"; + std::cout << " If " +- "relocating an exe, this is not required.\n"; +- std::cout << " --full = " ++ "relocating an exe or dll plugin, this may not be required.\n"; ++ std::cout << " --full = " + "Relocate dynamic references inside\n"; + std::cout << " " + "the pe in addition to re-generating\n"; + std::cout << " " + "the import library\n"; + std::cout << " " +- "Note: this is assumed to be true if\n"; +- std::cout << " " +- "relocating an executable.\n"; +- std::cout << " If " +- "an executable is relocated, no import\n"; +- std::cout << " " +- "library operations are performed.\n"; +- std::cout << " " +- "When relocating a DLL, the import library for\n"; ++ "When relocating a PE, the import library for\n"; + std::cout << " " + "said library is regenerated and the old imp lib\n"; + std::cout << " " +diff --git a/src/ld.cxx b/src/ld.cxx +index 8669273..abb3299 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -32,61 +32,70 @@ DWORD LdInvocation::InvokeToolchain() { + {this->command_args, this->include_args, this->lib_args, + this->lib_dir_args, this->obj_args})); + link_run.Parse(); +- // We're creating a dll, we need to create an appropriate import lib +- if (!link_run.IsExeLink()) { +- std::string const imp_lib_name = link_run.get_implib_name(); +- std::string dll_name; +- try { +- dll_name = link_run.get_mangled_out(); +- } catch (const NameTooLongError& e) { +- std::cerr << "Unable to mangle PE " << link_run.get_out() +- << " name is too long\n"; +- return ExitConditions::NORMALIZE_NAME_FAILURE; +- } +- std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; +- std::string def = "-def "; +- std::string piped_args = link_run.get_lib_link_args(); +- // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + dll_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); +- this->rpath_executor.Execute(); +- DWORD const err_code = this->rpath_executor.Join(); +- if (err_code != 0) { +- return err_code; +- } +- CoffReaderWriter coff_reader(abs_out_imp_lib_name); +- CoffParser coff(&coff_reader); +- if (!coff.Parse()) { +- debug("Failed to parse COFF file: " + abs_out_imp_lib_name); +- return ExitConditions::COFF_PARSE_FAILURE; +- } +- if (!coff.NormalizeName(dll_name)) { +- debug("Failed to normalize name for COFF file: " + +- abs_out_imp_lib_name); +- return ExitConditions::NORMALIZE_NAME_FAILURE; +- } +- debug("Renaming library from " + abs_out_imp_lib_name + " to " + +- imp_lib_name); +- int const remove_exitcode = std::remove(imp_lib_name.c_str()); +- if (remove_exitcode) { +- debug("Failed to remove original import library with exit code: " + +- remove_exitcode); +- return ExitConditions::LIB_REMOVE_FAILURE; +- } +- int const rename_exitcode = +- std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); +- if (rename_exitcode) { +- debug("Failed to rename temporary import library with exit code: " + +- rename_exitcode); +- return ExitConditions::FILE_RENAME_FAILURE; +- } ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // If there is no implib, we don't need to bother ++ // trying to rename ++ if (!fileExists(imp_lib_name)) { ++ // There are numerous contexts in which a PE file ++ // may not export symbols, some are bugs in the ++ // upstream project, most are valid, all are not ++ // the concern of this wrapper ++ return 0; ++ } ++ std::string pe_name; ++ try { ++ pe_name = link_run.get_mangled_out(); ++ } catch (const NameTooLongError& e) { ++ std::cerr << "Unable to mangle PE " << link_run.get_out() ++ << " name is too long\n"; ++ return ExitConditions::NORMALIZE_NAME_FAILURE; ++ } ++ std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; ++ std::string const def_file = ++ link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); ++ std::string const def = "-def" + def_file; ++ std::string const piped_args = link_run.get_lib_link_args(); ++ // create command line to generate new import lib ++ this->rpath_executor = ++ ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ ++ {def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ {link_run.get_rsp_file()}, ++ this->obj_args, ++ this->lib_args, ++ this->lib_dir_args, ++ })); ++ this->rpath_executor.Execute(); ++ DWORD const err_code = this->rpath_executor.Join(); ++ if (err_code != 0) { ++ return err_code; ++ } ++ CoffReaderWriter coff_reader(abs_out_imp_lib_name); ++ CoffParser coff(&coff_reader); ++ if (!coff.Parse()) { ++ debug("Failed to parse COFF file: " + abs_out_imp_lib_name); ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ if (!coff.NormalizeName(pe_name)) { ++ debug("Failed to normalize name for COFF file: " + ++ abs_out_imp_lib_name); ++ return ExitConditions::NORMALIZE_NAME_FAILURE; ++ } ++ debug("Renaming library from " + abs_out_imp_lib_name + " to " + ++ imp_lib_name); ++ int const remove_exitcode = std::remove(imp_lib_name.c_str()); ++ if (remove_exitcode) { ++ debug("Failed to remove original import library with exit code: " + ++ remove_exitcode); ++ return ExitConditions::LIB_REMOVE_FAILURE; ++ } ++ int const rename_exitcode = ++ std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); ++ if (rename_exitcode) { ++ debug("Failed to rename temporary import library with exit code: " + ++ rename_exitcode); ++ return ExitConditions::FILE_RENAME_FAILURE; + } + return ret_code; + } +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 1a96b5a..4ed82be 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,10 +3,12 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ +- +-#include "linker_invocation.h" ++#include ++#include ++#include + #include + #include ++#include "linker_invocation.h" + #include "utils.h" + + /** +@@ -46,11 +48,10 @@ void LinkerInvocation::Parse() { + this->implibname_ = implib_line[1]; + } else if (endswith(normal_token, ".lib")) { + this->libs_.push_back(*token); +- } else if (normal_token == "/dll" || normal_token == "-dll") { ++ } else if (normal_token == "dll") { + this->is_exe_ = false; +- } else if (startswith(normal_token, "-out") || +- startswith(normal_token, "/out")) { +- this->output_ = split(*token, ":", 1)[1]; ++ } else if (startswith(normal_token, "out")) { ++ this->output_ = split(*token, ":")[1]; + } else if (endswith(normal_token, ".obj")) { + this->objs_.push_back(*token); + } else if (startswith(normal_token, "@") && +@@ -60,12 +61,25 @@ void LinkerInvocation::Parse() { + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits + this->rsp_file_ = *token; ++ } else if (startswith(normal_token, "def")) { ++ this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != + this->piped_args_.end()) { +- this->piped_args_.at(normal_token).emplace_back(normal_token); ++ this->piped_args_.at(normal_token).emplace_back(*token); + } + } ++ // If we have a def file and no name, attempt to ++ // scrape the def file for a name to be sure ++ // we respect the intended project name ++ // vs overriding via the CLI ++ if (!this->def_file_.empty() && this->output_.empty()) { ++ this->processDefFile(); ++ } + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; ++ // If output wasn't defined on the command line ++ // or the def file ++ // compute it based on the same logic as the linker ++ // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +@@ -73,14 +87,79 @@ void LinkerInvocation::Parse() { + std::string const filename = split(name_obj, "\\").back(); + this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; + } +- this->name_ = strip(this->output_, ext); + if (this->implibname_.empty()) { +- this->implibname_ = this->name_ + ".lib"; ++ std::string const name = strip(this->output_, ext); ++ this->implibname_ = name + ".lib"; + } + } + +-std::string LinkerInvocation::get_name() { +- return this->name_; ++/** ++ * processDefFile reads a def file passed to the linker ++ * looking for either LIBRARY or NAME keywords ++ * If found the LinkerInvocation name_ attribute is assigned ++ * to the def file defined library name ++ * and the def file is re-written without that name for use ++ * if our lib pass so we can easily compose an absolute path'd ++ * version of that name ++ */ ++void LinkerInvocation::processDefFile() { ++ ++ // Def from link line ++ std::ifstream def_in(this->def_file_); ++ if (!def_in) { ++ std::cerr << "Error: Could not open input def file: " << this->def_file_ ++ << "\n"; ++ } ++ ++ std::string line; ++ bool def_file_export_name = false; ++ StrList exports; ++ ++ while (std::getline(def_in, line)) { ++ std::stringstream def_line(line); ++ std::string keyword; ++ def_line >> keyword; ++ ++ // extract the intended output library name, overwrite ++ // default name derived from first obj file ++ // We can leave this def file is as we override on the ++ // CLI ++ // Name renames exes ++ // Library renames DLLs ++ // These def keywords override the command line use ++ // of /DLL ++ if (keyword == "NAME") { ++ this->is_exe_ = true; ++ def_line >> this->pe_name_; ++ this->pe_name_ = this->pe_name_ + ".exe"; ++ def_file_export_name = true; ++ } else if (keyword == "LIBRARY") { ++ this->is_exe_ = false; ++ def_line >> this->pe_name_; ++ this->pe_name_ = this->pe_name_ + ".dll"; ++ def_file_export_name = true; ++ } else { ++ exports.push_back(line); ++ } ++ } ++ if (def_file_export_name) { ++ const std::string def_name = stem(this->def_file_); ++ const std::string def_path = ++ this->def_file_.substr(0, this->def_file_.find(def_name)); ++ const std::string rename_def = def_path + def_name + "-rename.def"; ++ ++ std::ofstream def_out(rename_def); ++ if (!def_out) { ++ std::cerr << "Error: could not open output def file: " << rename_def ++ << "\n"; ++ } ++ for (const auto& line : exports) { ++ def_out << line << "\n"; ++ } ++ def_out.close(); ++ this->def_file_ = rename_def; ++ } ++ def_in.close(); + } + + std::string LinkerInvocation::get_implib_name() { +@@ -112,13 +191,13 @@ std::string LinkerInvocation::get_rsp_file() { + } + + std::string LinkerInvocation::get_out() { +- return this->output_; ++ return this->pe_name_.empty() ? this->output_ : this->pe_name_; + } + + std::string LinkerInvocation::get_mangled_out() { +- return mangle_name(this->output_); ++ return mangle_name(this->get_out()); + } + + bool LinkerInvocation::IsExeLink() { +- return this->is_exe_ || endswith(this->output_, ".exe"); ++ return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index d195afe..a92a538 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -16,7 +16,6 @@ class LinkerInvocation { + ~LinkerInvocation() = default; + void Parse(); + bool IsExeLink(); +- std::string get_name(); + std::string get_out(); + std::string get_mangled_out(); + std::string get_implib_name(); +@@ -25,9 +24,10 @@ class LinkerInvocation { + std::string get_lib_link_args(); + + private: ++ void processDefFile(); + std::string line_; + StrList tokens_; +- std::string name_; ++ std::string pe_name_; + std::string implibname_; + std::string def_file_; + std::string rsp_file_; +@@ -36,9 +36,8 @@ class LinkerInvocation { + StrList objs_; + bool is_exe_; + std::map piped_args_ = { +- {"def", {}}, {"export", {}}, {"include", {}}, +- {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, +- {"nodefaultlib", {}}, {"subsystem", {}}, {"verbose", {}}, +- {"wx", {}}, ++ {"export", {}}, {"include", {}}, {"libpath", {}}, ++ {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, ++ {"subsystem", {}}, {"verbose", {}}, {"wx", {}}, + }; + }; +\ No newline at end of file +diff --git a/src/main.cxx b/src/main.cxx +index 45bac48..1fb0b63 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -37,8 +37,6 @@ int main(int argc, const char* argv[]) { + patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); +- bool const is_exe = +- has_pe ? endswith(patch_args.at("pe"), ".exe") : false; + bool const debug = !(patch_args.find("debug") == patch_args.end()); + bool const is_validate = + !(patch_args.find("verify") == patch_args.end()); +@@ -49,25 +47,13 @@ int main(int argc, const char* argv[]) { + std::cout << "No binaries to operate on... nothing to do\n"; + return ExitConditions::CLI_FAILURE; + } +- if (is_exe && !full) { +- std::cout << "Executable file provided but --full not specified, " +- "nothing to do...\n"; +- return ExitConditions::CLI_FAILURE; +- } +- // The only scenario its ok to have a dll/exe and no coff is when we're creating a cache +- // entry +- if (!is_exe && !has_coff && !deploy) { +- std::cout << "Attempting to relocate DLL without coff file, please " +- "provide a coff file.\n"; +- return ExitConditions::CLI_FAILURE; +- } + if (is_validate && !has_coff) { + std::cout << "Attempting to validate without a coff file, nothing " + "to validate\n"; + return ExitConditions::CLI_FAILURE; + } + if (report && !has_coff) { +- std::cout << "Attempting to report without a binary, nothing to " ++ std::cout << "Attempting to report without a coff file, nothing to " + "report...\n"; + return ExitConditions::CLI_FAILURE; + } +diff --git a/src/utils.cxx b/src/utils.cxx +index 49db7f4..ea48bfb 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -6,6 +6,7 @@ + #include "utils.h" + #include + #include ++#include + #include + #include + #include +@@ -196,6 +197,27 @@ std::string join(const StrList& args, const std::string& join_char) { + return joined_path; + } + ++/** ++ * @brief Removes trailing extenion from a path ++ * i.e. from /path/to/my/file.txt.tmp this method ++ * would remove .tmp and return ++ * /path/to/my/file.txt ++ * @param path - path to have trailing ext removed from ++ * ++ * @return path with last ext removed ++ */ ++std::string stripLastExt(const std::string& path) { ++ // ensure we're only operating on file component ++ const std::string base = basename(path); ++ const size_t ext_pos = base.rfind('.'); ++ std::string path_no_ext; ++ if (ext_pos != std::string::npos) { ++ const size_t ext_len = base.length() - ext_pos; ++ path_no_ext = path.substr(0, path.length() - ext_len); ++ } ++ return path_no_ext; ++} ++ + void StripPathAndExe(std::string& command) { + StripPath(command); + StripExe(command); +@@ -224,6 +246,14 @@ void lower(std::string& str) { + }); + } + ++/** ++ * Quotes str as needed ++ * If str has existing escaped quotes, or a space/reserved character ++ * Escape escaped quotes using an escaped backslash preceding the escaped ++ * quote. Escape reserved characters by quoting the entire string ++ * ++ * Return the escaped string ++ */ + std::string quoteAsNeeded(std::string& str) { + // Note: the ordering if these two conditionals is important + // If the second conditional is executed first, the first +@@ -330,7 +360,7 @@ std::string regexReplace( + * or an empty string as appropriate + */ + std::string GetSpackEnv(const char* env) { +- char* env_val = getenv(env); ++ char const* env_val = getenv(env); + return env_val ? env_val : std::string(); + } + +@@ -448,8 +478,10 @@ bool isCommandArg(const std::string& arg, const std::string& command) { + void normalArg(std::string& arg) { + // first normalize capitalization + lower(arg); ++ // strip any leading/trailing quotes ++ arg = strip(lstrip(arg, "\""), "\""); + // strip leading / and - +- arg = strip(strip(arg, "-"), "/"); ++ arg = lstrip(lstrip(arg, "-"), "/"); + } + + std::string reportLastError() { +@@ -617,7 +649,7 @@ std::string mangle_name(const std::string& name) { + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +- char* padded_path = ++ char const* padded_path = + pad_path(chr_abs_out, static_cast(abs_out.length())); + mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); + +@@ -626,6 +658,13 @@ std::string mangle_name(const std::string& name) { + return mangled_abs_out; + } + ++bool fileExists(const std::string& fname) { ++ std::ifstream file(fname); ++ bool const exists = file.good(); ++ file.close(); ++ return exists; ++} ++ + /** + * Determines whether a string contains path characters + * \param name string to check for path characters +@@ -649,7 +688,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -791,7 +830,7 @@ int get_slash_name_length(const char* slash_name) { + } + + char* findstr(char* search_str, const char* substr, size_t size) { +- char* search = search_str; ++ char* search = search_str; // NOLINT + size_t const str_size = strlen(substr); + while (search < search_str + size) { + if (!strncmp(search, substr, str_size)) { +diff --git a/src/utils.h b/src/utils.h +index e2f5a80..b04d929 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -110,6 +110,8 @@ void StripPath(std::string& command); + // Strips .exe extension from path + void StripExe(std::string& command); + ++std::string stripLastExt(const std::string& path); ++ + // Drives both StripPath and StripExe on the same path + // resulting in a parentless, non exe extensioned path + void StripPathAndExe(std::string& command); +@@ -132,6 +134,8 @@ char* findstr(char* search_str, const char* substr, size_t size); + // side effects on Windows + void quoteList(StrList& args); + ++std::string quoteAsNeeded(std::string& str); ++ + /// @brief Searches a sections of a string for a given regex using provided + /// options to control search behavior + /// @param searchDomain - string to be searched +@@ -193,6 +197,15 @@ void replace_special_characters(char* mangled, size_t len); + bool SpackInstalledLib(const std::string& lib); + + // File and File handle helpers // ++/** ++ * @brief Returns boolean indicating whether ++ * the given file exists ++ * ++ * @param fname file to check for existence ++ * ++ * @return true if fname exists, false otherwise ++ */ ++bool fileExists(const std::string& fname); + + // Returns File offset given RVA + DWORD RvaToFileOffset(PIMAGE_SECTION_HEADER& section_header, +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index dd385c6..f98e063 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,7 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include ++#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -137,7 +137,8 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { + * On extraction, we find dll names with the Spack sigil and rename (and repad) them with + * the correct absolute path to the requisite DLL on the new host system. + * +- * This approach is heavily based on https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/pe-file-header-parser-in-c++#first-dll-name ++ * This approach is heavily based on ++ * https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/pe-file-header-parser-in-c++#first-dll-name + * + * \param pe_in the PE file for which to perform the imported DLL rename procedure + * +@@ -253,7 +254,6 @@ LibRename::LibRename(std::string p_exe, std::string coff, bool full, + pe(std::move(p_exe)), + deploy(deploy), + coff(std::move(coff)) { +- this->is_exe = endswith(this->pe, ".exe"); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -353,9 +353,8 @@ bool LibRename::ComputeDefFile() { + */ + bool LibRename::ExecuteRename() { + // If we're not deploying, we're extracting +- // recompute the .def and .lib for dlls +- // exes do not typically have import libs so we don't handle +- // that case ++ // recompute the .def and .lib for dlls and optionally ++ // exes + // We do not bother with defs for things that don't have + // import libraries + if (!this->deploy && !this->coff.empty()) { +diff --git a/src/winrpath.h b/src/winrpath.h +index 4876e1a..e166465 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -55,5 +55,4 @@ class LibRename { + bool full; + bool deploy; + bool replace; +- bool is_exe; + }; +diff --git a/test/calc.def b/test/calc.def +new file mode 100644 +index 0000000..59f93ce +--- /dev/null ++++ b/test/calc.def +@@ -0,0 +1,4 @@ ++LIBRARY calcdef ++ ++EXPORTS ++ add +\ No newline at end of file +diff --git a/test/include/calc header/calc.h b/test/include/calc header/calc.h +index d7d3975..855dc7c 100644 +--- a/test/include/calc header/calc.h ++++ b/test/include/calc header/calc.h +@@ -7,6 +7,8 @@ + + #ifdef CALC_EXPORTS + #define CALC_API __declspec(dllexport) ++#elif CALC_DEF_EXPORTS ++#define CALC_API + #else + #define CALC_API __declspec(dllimport) + #endif +diff --git a/test/main2.cxx b/test/main2.cxx +new file mode 100644 +index 0000000..e2ba16a +--- /dev/null ++++ b/test/main2.cxx +@@ -0,0 +1,17 @@ ++/** ++ * Copyright Spack Project Developers. See COPYRIGHT file for details. ++ * ++ * SPDX-License-Identifier: (Apache-2.0 OR MIT) ++ */ ++#include "main2.h" ++#include "calc header/calc.h" ++ ++extern "C" int sub(const int& a, const int& b) { ++ return a - b; ++} ++ ++int main(int /*argc*/, char** /*argv*/) { ++ add(1, 2); ++ sub(2, 1); ++ return 0; ++} +\ No newline at end of file +diff --git a/test/main2.h b/test/main2.h +new file mode 100644 +index 0000000..ba381ba +--- /dev/null ++++ b/test/main2.h +@@ -0,0 +1,14 @@ ++/** ++ * Copyright Spack Project Developers. See COPYRIGHT file for details. ++ * ++ * SPDX-License-Identifier: (Apache-2.0 OR MIT) ++ */ ++#pragma once ++ ++#ifdef MAIN_EXPORTS ++#define MAIN_API __declspec(dllexport) ++#else ++#define MAIN_API __declspec(dllimport) ++#endif ++ ++extern "C" MAIN_API int sub(const int& a, const int& b); +\ No newline at end of file +diff --git a/test/main3.cxx b/test/main3.cxx +new file mode 100644 +index 0000000..232435d +--- /dev/null ++++ b/test/main3.cxx +@@ -0,0 +1,13 @@ ++/** ++ * Copyright Spack Project Developers. See COPYRIGHT file for details. ++ * ++ * SPDX-License-Identifier: (Apache-2.0 OR MIT) ++ */ ++#include "main2.h" ++#include "calc header/calc.h" ++ ++int main(int /*argc*/, char** /*argv*/) { ++ sub(2, 1); ++ add(1, 2); ++ return 0; ++} +\ No newline at end of file +diff --git a/test/src file/calc.cxx b/test/src file/calc.cxx +index 09ebd77..1bfce7c 100644 +--- a/test/src file/calc.cxx ++++ b/test/src file/calc.cxx +@@ -4,7 +4,7 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + +-#if defined(CALC_HEADER) ++#ifdef CALC_HEADER + #include CALC_HEADER /* "calc.h" */ + #else + #include "calc header/calc.h" diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index bd85ff9d415..78eaabc44d3 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,22 +57,9 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: version("develop", branch="main") - version("0.1.0", sha256="4eab2cb48bb83edb88780517c9dfa55778f8adc555ec4939cb73e2d05fed5a5a") with when("@develop platform=windows"): - patch("c_cxx.patch") - patch("improve_def_arg_forwarding.patch") - # available in 0.1.1 - with when("@0.1.0 platform=windows"): - patch("fixup11.patch") - patch("quote_args.patch") - patch("include_regex.patch") - patch("pipe.patch") - patch("rsp.patch") - patch("paths.patch") - patch("path_fixup.patch") - patch("spack-installed-libs.patch") - patch("strip_pad.patch") + patch("def-and-exe-enhanced-support.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From 2d7dd6944f2c8031f11844e75395010ae19cdd43 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 9 Dec 2025 15:32:30 -0500 Subject: [PATCH 17/81] update package Signed-off-by: John Parent --- .../packages/compiler_wrapper/c_cxx.patch | 16 - .../def-and-exe-enhanced-support.patch | 915 ------------ .../packages/compiler_wrapper/fixup11.patch | 1237 ----------------- .../improve_def_arg_forwarding.patch | 113 -- .../compiler_wrapper/include_regex.patch | 12 - .../packages/compiler_wrapper/long_name.patch | 264 ---- .../compiler_wrapper/long_path_support.patch | 475 ------- .../packages/compiler_wrapper/package.py | 14 +- .../compiler_wrapper/path_fixup.patch | 24 - .../packages/compiler_wrapper/paths.patch | 325 ----- .../packages/compiler_wrapper/pipe.patch | 221 --- .../compiler_wrapper/quote_args.patch | 60 - .../packages/compiler_wrapper/quoting.patch | 35 - .../packages/compiler_wrapper/rc_dll_id.patch | 474 +++++++ .../compiler_wrapper/rc_dll_stage_id.patch | 383 +++++ .../packages/compiler_wrapper/rsp.patch | 62 - .../spack-installed-libs.patch | 46 - .../packages/compiler_wrapper/strip_pad.patch | 35 - 18 files changed, 859 insertions(+), 3852 deletions(-) delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch deleted file mode 100644 index aeac5374a24..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/c_cxx.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/src/cl.cxx b/src/cl.cxx -index 33fbedf..88c8c61 100644 ---- a/src/cl.cxx -+++ b/src/cl.cxx -@@ -8,5 +8,10 @@ - #include "spack_env.h" - - void ClInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { -- this->command = spackenv.SpackCC; -+ // C and CXX compiler executables are the same for MSVC (both cl, hence the class name) -+ // However, depending on how a package depends on the languages, one or both -+ // may be present in the env. Arbitrarily prefer the C compiler as the choice -+ // truly doesn't matter -+ this->command = -+ !spackenv.SpackCC.empty() ? spackenv.SpackCC : spackenv.SpackCXX; - } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch deleted file mode 100644 index 7148b26e77e..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/def-and-exe-enhanced-support.patch +++ /dev/null @@ -1,915 +0,0 @@ -diff --git a/Makefile b/Makefile -index e31f957..45f668f 100644 ---- a/Makefile -+++ b/Makefile -@@ -66,6 +66,9 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -+ echo "-------------------" -+ echo "Running Test Setup" -+ echo "-------------------" - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -77,6 +80,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -+ echo "--------------------" -+ echo "Building Test Sample" -+ echo "--------------------" - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -88,6 +94,9 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -+ echo "--------------------" -+ echo "Running Wrapper Test" -+ echo "--------------------" - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -101,6 +110,9 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -+ echo "--------------------------" -+ echo "Running Relocate Exe Test" -+ echo "--------------------------" - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -@@ -112,6 +124,9 @@ test_relocate_exe: build_and_check_test_sample - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -+ echo "--------------------------" -+ echo "Running Relocate DLL test" -+ echo "--------------------------" - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -127,6 +142,9 @@ test_relocate_dll: build_and_check_test_sample - cd ../.. - - test_pipe_overflow: build_and_check_test_sample -+ echo "--------------------" -+ echo " Pipe overflow test" -+ echo "--------------------" - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" -@@ -136,12 +154,18 @@ build_zerowrite_test: test\writezero.obj - link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe - - test_zerowrite: build_zerowrite_test -+ echo "-----------------------" -+ echo "Running zerowrite test" -+ echo "-----------------------" - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -+ echo "------------------------" -+ echo "Running long paths test" -+ echo "------------------------" - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -158,6 +182,9 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -+ echo "---------------------------------" -+ echo "Running relocate logn paths test" -+ echo "---------------------------------" - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -172,10 +199,49 @@ test_relocate_long_paths: test_long_paths - .\tester.exe - cd ../../../.. - -+test_def_file_name_override: -+ mkdir tmp\test\def\def_override -+ xcopy /E test\include tmp\test\def\def_override -+ xcopy /E "test\src file" tmp\test\def\def_override -+ xcopy test\main.cxx tmp\test\def\def_override -+ xcopy test\calc.def tmp\test\def\def_override -+ cd tmp\test\def\def_override -+ copy ..\..\..\..\cl.exe cl.exe -+ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe -+ cl /c /EHsc "calc.cxx" /DCALC_DEF_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include -+ cl /c /EHsc main.cxx /I include -+ link $(LFLAGS) /DEF:calc.def calc.obj /DLL -+ link $(LFLAGS) main.obj calc.lib /out:tester.exe -+ tester.exe -+ cd ../../../.. -+ -+test_exe_with_exports: -+ echo ------------------------------ -+ echo Running exe with exports test -+ echo ------------------------------ -+ mkdir tmp\test\exe_with_exports -+ xcopy /E test\include tmp\test\exe_with_exports -+ xcopy /E "test\src file" tmp\test\exe_with_exports -+ xcopy test\main2.h tmp\test\exe_with_exports -+ xcopy test\main2.cxx tmp\test\exe_with_exports -+ xcopy test\main3.cxx tmp\test\exe_with_exports -+ cd tmp\test\exe_with_exports -+ copy ..\..\..\cl.exe cl.exe -+ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe -+ cl /c /EHsc "calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include -+ cl /c /EHsc main2.cxx /DMAIN_EXPORTS /I include -+ cl /c /EHsc main3.cxx /I include -+ link $(LFLAGS) calc.obj /out:calc.dll /DLL -+ link $(LFLAGS) main2.obj calc.lib /out:tester1.exe -+ link $(LFLAGS) main3.obj calc.lib tester1.lib /out:tester2.exe -+ tester1.exe -+ tester2.exe -+ cd ../../.. -+ - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_exe_with_exports test_def_file_name_override test_long_paths test_pipe_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 3cda566..25f4b28 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -292,7 +292,7 @@ bool CoffParser::ValidateLongName(coff_member* member, int size) { - } - // If a name has an object file, this is not an import - // member -- char* obj_res = findstr(member->data, ".obj", size); -+ char const* obj_res = findstr(member->data, ".obj", size); - return obj_res == nullptr; - } - -@@ -326,7 +326,7 @@ void CoffParser::NormalizeSectionNames(const std::string& name, char* section, - size_t data_size) { - size_t const name_len = name.size(); - char* section_search_start = section; -- char* search_terminator = section + data_size; -+ char const* search_terminator = section + data_size; - ptrdiff_t offset = 0; - while (section_search_start && (section_search_start < search_terminator)) { - // findstr's final parameter takes the size of the search domain -@@ -378,8 +378,7 @@ bool CoffParser::NormalizeName(std::string& name) { - // The dll is found with and without an extenion, depending on the context of the location - // i.e. in the section data, it can be found with both an extension and extensionless - // whereas in the symbol table or linker member strings, it's always found without an extension -- std::string const name_no_dll = strip(name, ".dll"); -- std::string const name_no_ext = strip(name_no_dll, ".DLL"); -+ std::string const name_no_ext = stripLastExt(name); - // Flag allowing us to skip multiple attempts - // to rename the long names member this name - bool long_name_renamed = false; -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 76b95af..38bd760 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -28,20 +28,6 @@ int redefinedArgCheck(const std::map& args, - return 0; - } - --/** -- * Check for the presense of an argument in the argument map -- */ --int checkArgumentPresence(const std::map& args, -- const char* val, bool required = true) { -- if (args.find(val) == args.end()) { -- std::cerr << "Warning! Argument (" << val << ") not present\n"; -- if (required) { -- return 0; -- } -- } -- return 1; --} -- - bool print_help() { - std::cout << "Spack's Windows compiler wrapper\n"; - std::cout << "Version: " << STRING(MSVC_WRAPPER_VERSION) << "\n"; -@@ -82,28 +68,20 @@ bool print_help() { - "this file:\n"; - std::cout << "\n"; - std::cout << " Options:\n"; -- std::cout << " --pe = PE " -+ std::cout << " --pe = PE " - "(dll/exe) file to be relocated\n"; -- std::cout << " [--coff ] = " -+ std::cout << " --coff = " - "COFF (import library) file to be relocated\n"; - std::cout << " If " -- "relocating an exe, this is not required.\n"; -- std::cout << " --full = " -+ "relocating an exe or dll plugin, this may not be required.\n"; -+ std::cout << " --full = " - "Relocate dynamic references inside\n"; - std::cout << " " - "the pe in addition to re-generating\n"; - std::cout << " " - "the import library\n"; - std::cout << " " -- "Note: this is assumed to be true if\n"; -- std::cout << " " -- "relocating an executable.\n"; -- std::cout << " If " -- "an executable is relocated, no import\n"; -- std::cout << " " -- "library operations are performed.\n"; -- std::cout << " " -- "When relocating a DLL, the import library for\n"; -+ "When relocating a PE, the import library for\n"; - std::cout << " " - "said library is regenerated and the old imp lib\n"; - std::cout << " " -diff --git a/src/ld.cxx b/src/ld.cxx -index 8669273..abb3299 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -32,61 +32,70 @@ DWORD LdInvocation::InvokeToolchain() { - {this->command_args, this->include_args, this->lib_args, - this->lib_dir_args, this->obj_args})); - link_run.Parse(); -- // We're creating a dll, we need to create an appropriate import lib -- if (!link_run.IsExeLink()) { -- std::string const imp_lib_name = link_run.get_implib_name(); -- std::string dll_name; -- try { -- dll_name = link_run.get_mangled_out(); -- } catch (const NameTooLongError& e) { -- std::cerr << "Unable to mangle PE " << link_run.get_out() -- << " name is too long\n"; -- return ExitConditions::NORMALIZE_NAME_FAILURE; -- } -- std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; -- std::string def = "-def "; -- std::string piped_args = link_run.get_lib_link_args(); -- // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + dll_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -- this->rpath_executor.Execute(); -- DWORD const err_code = this->rpath_executor.Join(); -- if (err_code != 0) { -- return err_code; -- } -- CoffReaderWriter coff_reader(abs_out_imp_lib_name); -- CoffParser coff(&coff_reader); -- if (!coff.Parse()) { -- debug("Failed to parse COFF file: " + abs_out_imp_lib_name); -- return ExitConditions::COFF_PARSE_FAILURE; -- } -- if (!coff.NormalizeName(dll_name)) { -- debug("Failed to normalize name for COFF file: " + -- abs_out_imp_lib_name); -- return ExitConditions::NORMALIZE_NAME_FAILURE; -- } -- debug("Renaming library from " + abs_out_imp_lib_name + " to " + -- imp_lib_name); -- int const remove_exitcode = std::remove(imp_lib_name.c_str()); -- if (remove_exitcode) { -- debug("Failed to remove original import library with exit code: " + -- remove_exitcode); -- return ExitConditions::LIB_REMOVE_FAILURE; -- } -- int const rename_exitcode = -- std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); -- if (rename_exitcode) { -- debug("Failed to rename temporary import library with exit code: " + -- rename_exitcode); -- return ExitConditions::FILE_RENAME_FAILURE; -- } -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // If there is no implib, we don't need to bother -+ // trying to rename -+ if (!fileExists(imp_lib_name)) { -+ // There are numerous contexts in which a PE file -+ // may not export symbols, some are bugs in the -+ // upstream project, most are valid, all are not -+ // the concern of this wrapper -+ return 0; -+ } -+ std::string pe_name; -+ try { -+ pe_name = link_run.get_mangled_out(); -+ } catch (const NameTooLongError& e) { -+ std::cerr << "Unable to mangle PE " << link_run.get_out() -+ << " name is too long\n"; -+ return ExitConditions::NORMALIZE_NAME_FAILURE; -+ } -+ std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -+ std::string const def_file = -+ link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -+ std::string const def = "-def" + def_file; -+ std::string const piped_args = link_run.get_lib_link_args(); -+ // create command line to generate new import lib -+ this->rpath_executor = -+ ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -+ {def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ {link_run.get_rsp_file()}, -+ this->obj_args, -+ this->lib_args, -+ this->lib_dir_args, -+ })); -+ this->rpath_executor.Execute(); -+ DWORD const err_code = this->rpath_executor.Join(); -+ if (err_code != 0) { -+ return err_code; -+ } -+ CoffReaderWriter coff_reader(abs_out_imp_lib_name); -+ CoffParser coff(&coff_reader); -+ if (!coff.Parse()) { -+ debug("Failed to parse COFF file: " + abs_out_imp_lib_name); -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ if (!coff.NormalizeName(pe_name)) { -+ debug("Failed to normalize name for COFF file: " + -+ abs_out_imp_lib_name); -+ return ExitConditions::NORMALIZE_NAME_FAILURE; -+ } -+ debug("Renaming library from " + abs_out_imp_lib_name + " to " + -+ imp_lib_name); -+ int const remove_exitcode = std::remove(imp_lib_name.c_str()); -+ if (remove_exitcode) { -+ debug("Failed to remove original import library with exit code: " + -+ remove_exitcode); -+ return ExitConditions::LIB_REMOVE_FAILURE; -+ } -+ int const rename_exitcode = -+ std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); -+ if (rename_exitcode) { -+ debug("Failed to rename temporary import library with exit code: " + -+ rename_exitcode); -+ return ExitConditions::FILE_RENAME_FAILURE; - } - return ret_code; - } -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 1a96b5a..4ed82be 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,10 +3,12 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -- --#include "linker_invocation.h" -+#include -+#include -+#include - #include - #include -+#include "linker_invocation.h" - #include "utils.h" - - /** -@@ -46,11 +48,10 @@ void LinkerInvocation::Parse() { - this->implibname_ = implib_line[1]; - } else if (endswith(normal_token, ".lib")) { - this->libs_.push_back(*token); -- } else if (normal_token == "/dll" || normal_token == "-dll") { -+ } else if (normal_token == "dll") { - this->is_exe_ = false; -- } else if (startswith(normal_token, "-out") || -- startswith(normal_token, "/out")) { -- this->output_ = split(*token, ":", 1)[1]; -+ } else if (startswith(normal_token, "out")) { -+ this->output_ = split(*token, ":")[1]; - } else if (endswith(normal_token, ".obj")) { - this->objs_.push_back(*token); - } else if (startswith(normal_token, "@") && -@@ -60,12 +61,25 @@ void LinkerInvocation::Parse() { - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits - this->rsp_file_ = *token; -+ } else if (startswith(normal_token, "def")) { -+ this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != - this->piped_args_.end()) { -- this->piped_args_.at(normal_token).emplace_back(normal_token); -+ this->piped_args_.at(normal_token).emplace_back(*token); - } - } -+ // If we have a def file and no name, attempt to -+ // scrape the def file for a name to be sure -+ // we respect the intended project name -+ // vs overriding via the CLI -+ if (!this->def_file_.empty() && this->output_.empty()) { -+ this->processDefFile(); -+ } - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -+ // If output wasn't defined on the command line -+ // or the def file -+ // compute it based on the same logic as the linker -+ // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -@@ -73,14 +87,79 @@ void LinkerInvocation::Parse() { - std::string const filename = split(name_obj, "\\").back(); - this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; - } -- this->name_ = strip(this->output_, ext); - if (this->implibname_.empty()) { -- this->implibname_ = this->name_ + ".lib"; -+ std::string const name = strip(this->output_, ext); -+ this->implibname_ = name + ".lib"; - } - } - --std::string LinkerInvocation::get_name() { -- return this->name_; -+/** -+ * processDefFile reads a def file passed to the linker -+ * looking for either LIBRARY or NAME keywords -+ * If found the LinkerInvocation name_ attribute is assigned -+ * to the def file defined library name -+ * and the def file is re-written without that name for use -+ * if our lib pass so we can easily compose an absolute path'd -+ * version of that name -+ */ -+void LinkerInvocation::processDefFile() { -+ -+ // Def from link line -+ std::ifstream def_in(this->def_file_); -+ if (!def_in) { -+ std::cerr << "Error: Could not open input def file: " << this->def_file_ -+ << "\n"; -+ } -+ -+ std::string line; -+ bool def_file_export_name = false; -+ StrList exports; -+ -+ while (std::getline(def_in, line)) { -+ std::stringstream def_line(line); -+ std::string keyword; -+ def_line >> keyword; -+ -+ // extract the intended output library name, overwrite -+ // default name derived from first obj file -+ // We can leave this def file is as we override on the -+ // CLI -+ // Name renames exes -+ // Library renames DLLs -+ // These def keywords override the command line use -+ // of /DLL -+ if (keyword == "NAME") { -+ this->is_exe_ = true; -+ def_line >> this->pe_name_; -+ this->pe_name_ = this->pe_name_ + ".exe"; -+ def_file_export_name = true; -+ } else if (keyword == "LIBRARY") { -+ this->is_exe_ = false; -+ def_line >> this->pe_name_; -+ this->pe_name_ = this->pe_name_ + ".dll"; -+ def_file_export_name = true; -+ } else { -+ exports.push_back(line); -+ } -+ } -+ if (def_file_export_name) { -+ const std::string def_name = stem(this->def_file_); -+ const std::string def_path = -+ this->def_file_.substr(0, this->def_file_.find(def_name)); -+ const std::string rename_def = def_path + def_name + "-rename.def"; -+ -+ std::ofstream def_out(rename_def); -+ if (!def_out) { -+ std::cerr << "Error: could not open output def file: " << rename_def -+ << "\n"; -+ } -+ for (const auto& line : exports) { -+ def_out << line << "\n"; -+ } -+ def_out.close(); -+ this->def_file_ = rename_def; -+ } -+ def_in.close(); - } - - std::string LinkerInvocation::get_implib_name() { -@@ -112,13 +191,13 @@ std::string LinkerInvocation::get_rsp_file() { - } - - std::string LinkerInvocation::get_out() { -- return this->output_; -+ return this->pe_name_.empty() ? this->output_ : this->pe_name_; - } - - std::string LinkerInvocation::get_mangled_out() { -- return mangle_name(this->output_); -+ return mangle_name(this->get_out()); - } - - bool LinkerInvocation::IsExeLink() { -- return this->is_exe_ || endswith(this->output_, ".exe"); -+ return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index d195afe..a92a538 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -16,7 +16,6 @@ class LinkerInvocation { - ~LinkerInvocation() = default; - void Parse(); - bool IsExeLink(); -- std::string get_name(); - std::string get_out(); - std::string get_mangled_out(); - std::string get_implib_name(); -@@ -25,9 +24,10 @@ class LinkerInvocation { - std::string get_lib_link_args(); - - private: -+ void processDefFile(); - std::string line_; - StrList tokens_; -- std::string name_; -+ std::string pe_name_; - std::string implibname_; - std::string def_file_; - std::string rsp_file_; -@@ -36,9 +36,8 @@ class LinkerInvocation { - StrList objs_; - bool is_exe_; - std::map piped_args_ = { -- {"def", {}}, {"export", {}}, {"include", {}}, -- {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, -- {"nodefaultlib", {}}, {"subsystem", {}}, {"verbose", {}}, -- {"wx", {}}, -+ {"export", {}}, {"include", {}}, {"libpath", {}}, -+ {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, -+ {"subsystem", {}}, {"verbose", {}}, {"wx", {}}, - }; - }; -\ No newline at end of file -diff --git a/src/main.cxx b/src/main.cxx -index 45bac48..1fb0b63 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -37,8 +37,6 @@ int main(int argc, const char* argv[]) { - patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); -- bool const is_exe = -- has_pe ? endswith(patch_args.at("pe"), ".exe") : false; - bool const debug = !(patch_args.find("debug") == patch_args.end()); - bool const is_validate = - !(patch_args.find("verify") == patch_args.end()); -@@ -49,25 +47,13 @@ int main(int argc, const char* argv[]) { - std::cout << "No binaries to operate on... nothing to do\n"; - return ExitConditions::CLI_FAILURE; - } -- if (is_exe && !full) { -- std::cout << "Executable file provided but --full not specified, " -- "nothing to do...\n"; -- return ExitConditions::CLI_FAILURE; -- } -- // The only scenario its ok to have a dll/exe and no coff is when we're creating a cache -- // entry -- if (!is_exe && !has_coff && !deploy) { -- std::cout << "Attempting to relocate DLL without coff file, please " -- "provide a coff file.\n"; -- return ExitConditions::CLI_FAILURE; -- } - if (is_validate && !has_coff) { - std::cout << "Attempting to validate without a coff file, nothing " - "to validate\n"; - return ExitConditions::CLI_FAILURE; - } - if (report && !has_coff) { -- std::cout << "Attempting to report without a binary, nothing to " -+ std::cout << "Attempting to report without a coff file, nothing to " - "report...\n"; - return ExitConditions::CLI_FAILURE; - } -diff --git a/src/utils.cxx b/src/utils.cxx -index 49db7f4..ea48bfb 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -6,6 +6,7 @@ - #include "utils.h" - #include - #include -+#include - #include - #include - #include -@@ -196,6 +197,27 @@ std::string join(const StrList& args, const std::string& join_char) { - return joined_path; - } - -+/** -+ * @brief Removes trailing extenion from a path -+ * i.e. from /path/to/my/file.txt.tmp this method -+ * would remove .tmp and return -+ * /path/to/my/file.txt -+ * @param path - path to have trailing ext removed from -+ * -+ * @return path with last ext removed -+ */ -+std::string stripLastExt(const std::string& path) { -+ // ensure we're only operating on file component -+ const std::string base = basename(path); -+ const size_t ext_pos = base.rfind('.'); -+ std::string path_no_ext; -+ if (ext_pos != std::string::npos) { -+ const size_t ext_len = base.length() - ext_pos; -+ path_no_ext = path.substr(0, path.length() - ext_len); -+ } -+ return path_no_ext; -+} -+ - void StripPathAndExe(std::string& command) { - StripPath(command); - StripExe(command); -@@ -224,6 +246,14 @@ void lower(std::string& str) { - }); - } - -+/** -+ * Quotes str as needed -+ * If str has existing escaped quotes, or a space/reserved character -+ * Escape escaped quotes using an escaped backslash preceding the escaped -+ * quote. Escape reserved characters by quoting the entire string -+ * -+ * Return the escaped string -+ */ - std::string quoteAsNeeded(std::string& str) { - // Note: the ordering if these two conditionals is important - // If the second conditional is executed first, the first -@@ -330,7 +360,7 @@ std::string regexReplace( - * or an empty string as appropriate - */ - std::string GetSpackEnv(const char* env) { -- char* env_val = getenv(env); -+ char const* env_val = getenv(env); - return env_val ? env_val : std::string(); - } - -@@ -448,8 +478,10 @@ bool isCommandArg(const std::string& arg, const std::string& command) { - void normalArg(std::string& arg) { - // first normalize capitalization - lower(arg); -+ // strip any leading/trailing quotes -+ arg = strip(lstrip(arg, "\""), "\""); - // strip leading / and - -- arg = strip(strip(arg, "-"), "/"); -+ arg = lstrip(lstrip(arg, "-"), "/"); - } - - std::string reportLastError() { -@@ -617,7 +649,7 @@ std::string mangle_name(const std::string& name) { - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -- char* padded_path = -+ char const* padded_path = - pad_path(chr_abs_out, static_cast(abs_out.length())); - mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); - -@@ -626,6 +658,13 @@ std::string mangle_name(const std::string& name) { - return mangled_abs_out; - } - -+bool fileExists(const std::string& fname) { -+ std::ifstream file(fname); -+ bool const exists = file.good(); -+ file.close(); -+ return exists; -+} -+ - /** - * Determines whether a string contains path characters - * \param name string to check for path characters -@@ -649,7 +688,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -791,7 +830,7 @@ int get_slash_name_length(const char* slash_name) { - } - - char* findstr(char* search_str, const char* substr, size_t size) { -- char* search = search_str; -+ char* search = search_str; // NOLINT - size_t const str_size = strlen(substr); - while (search < search_str + size) { - if (!strncmp(search, substr, str_size)) { -diff --git a/src/utils.h b/src/utils.h -index e2f5a80..b04d929 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -110,6 +110,8 @@ void StripPath(std::string& command); - // Strips .exe extension from path - void StripExe(std::string& command); - -+std::string stripLastExt(const std::string& path); -+ - // Drives both StripPath and StripExe on the same path - // resulting in a parentless, non exe extensioned path - void StripPathAndExe(std::string& command); -@@ -132,6 +134,8 @@ char* findstr(char* search_str, const char* substr, size_t size); - // side effects on Windows - void quoteList(StrList& args); - -+std::string quoteAsNeeded(std::string& str); -+ - /// @brief Searches a sections of a string for a given regex using provided - /// options to control search behavior - /// @param searchDomain - string to be searched -@@ -193,6 +197,15 @@ void replace_special_characters(char* mangled, size_t len); - bool SpackInstalledLib(const std::string& lib); - - // File and File handle helpers // -+/** -+ * @brief Returns boolean indicating whether -+ * the given file exists -+ * -+ * @param fname file to check for existence -+ * -+ * @return true if fname exists, false otherwise -+ */ -+bool fileExists(const std::string& fname); - - // Returns File offset given RVA - DWORD RvaToFileOffset(PIMAGE_SECTION_HEADER& section_header, -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index dd385c6..f98e063 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,7 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include -+#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -137,7 +137,8 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { - * On extraction, we find dll names with the Spack sigil and rename (and repad) them with - * the correct absolute path to the requisite DLL on the new host system. - * -- * This approach is heavily based on https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/pe-file-header-parser-in-c++#first-dll-name -+ * This approach is heavily based on -+ * https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/pe-file-header-parser-in-c++#first-dll-name - * - * \param pe_in the PE file for which to perform the imported DLL rename procedure - * -@@ -253,7 +254,6 @@ LibRename::LibRename(std::string p_exe, std::string coff, bool full, - pe(std::move(p_exe)), - deploy(deploy), - coff(std::move(coff)) { -- this->is_exe = endswith(this->pe, ".exe"); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -353,9 +353,8 @@ bool LibRename::ComputeDefFile() { - */ - bool LibRename::ExecuteRename() { - // If we're not deploying, we're extracting -- // recompute the .def and .lib for dlls -- // exes do not typically have import libs so we don't handle -- // that case -+ // recompute the .def and .lib for dlls and optionally -+ // exes - // We do not bother with defs for things that don't have - // import libraries - if (!this->deploy && !this->coff.empty()) { -diff --git a/src/winrpath.h b/src/winrpath.h -index 4876e1a..e166465 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -55,5 +55,4 @@ class LibRename { - bool full; - bool deploy; - bool replace; -- bool is_exe; - }; -diff --git a/test/calc.def b/test/calc.def -new file mode 100644 -index 0000000..59f93ce ---- /dev/null -+++ b/test/calc.def -@@ -0,0 +1,4 @@ -+LIBRARY calcdef -+ -+EXPORTS -+ add -\ No newline at end of file -diff --git a/test/include/calc header/calc.h b/test/include/calc header/calc.h -index d7d3975..855dc7c 100644 ---- a/test/include/calc header/calc.h -+++ b/test/include/calc header/calc.h -@@ -7,6 +7,8 @@ - - #ifdef CALC_EXPORTS - #define CALC_API __declspec(dllexport) -+#elif CALC_DEF_EXPORTS -+#define CALC_API - #else - #define CALC_API __declspec(dllimport) - #endif -diff --git a/test/main2.cxx b/test/main2.cxx -new file mode 100644 -index 0000000..e2ba16a ---- /dev/null -+++ b/test/main2.cxx -@@ -0,0 +1,17 @@ -+/** -+ * Copyright Spack Project Developers. See COPYRIGHT file for details. -+ * -+ * SPDX-License-Identifier: (Apache-2.0 OR MIT) -+ */ -+#include "main2.h" -+#include "calc header/calc.h" -+ -+extern "C" int sub(const int& a, const int& b) { -+ return a - b; -+} -+ -+int main(int /*argc*/, char** /*argv*/) { -+ add(1, 2); -+ sub(2, 1); -+ return 0; -+} -\ No newline at end of file -diff --git a/test/main2.h b/test/main2.h -new file mode 100644 -index 0000000..ba381ba ---- /dev/null -+++ b/test/main2.h -@@ -0,0 +1,14 @@ -+/** -+ * Copyright Spack Project Developers. See COPYRIGHT file for details. -+ * -+ * SPDX-License-Identifier: (Apache-2.0 OR MIT) -+ */ -+#pragma once -+ -+#ifdef MAIN_EXPORTS -+#define MAIN_API __declspec(dllexport) -+#else -+#define MAIN_API __declspec(dllimport) -+#endif -+ -+extern "C" MAIN_API int sub(const int& a, const int& b); -\ No newline at end of file -diff --git a/test/main3.cxx b/test/main3.cxx -new file mode 100644 -index 0000000..232435d ---- /dev/null -+++ b/test/main3.cxx -@@ -0,0 +1,13 @@ -+/** -+ * Copyright Spack Project Developers. See COPYRIGHT file for details. -+ * -+ * SPDX-License-Identifier: (Apache-2.0 OR MIT) -+ */ -+#include "main2.h" -+#include "calc header/calc.h" -+ -+int main(int /*argc*/, char** /*argv*/) { -+ sub(2, 1); -+ add(1, 2); -+ return 0; -+} -\ No newline at end of file -diff --git a/test/src file/calc.cxx b/test/src file/calc.cxx -index 09ebd77..1bfce7c 100644 ---- a/test/src file/calc.cxx -+++ b/test/src file/calc.cxx -@@ -4,7 +4,7 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - --#if defined(CALC_HEADER) -+#ifdef CALC_HEADER - #include CALC_HEADER /* "calc.h" */ - #else - #include "calc header/calc.h" diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch deleted file mode 100644 index cd1e84b7780..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/fixup11.patch +++ /dev/null @@ -1,1237 +0,0 @@ -diff --git a/Makefile b/Makefile -index 51fe35d..3480939 100644 ---- a/Makefile -+++ b/Makefile -@@ -22,7 +22,7 @@ PREFIX="$(MAKEDIR)\install\" - !ENDIF - - !IF "$(BUILD_TYPE)" == "DEBUG" --BUILD_CFLAGS = /Zi -+BUILD_CFLAGS = /Zi /fsanitize=address - BUILD_LINK = /DEBUG - !ENDIF - -@@ -58,8 +58,8 @@ setup_test: cl.exe - - build_and_check_test_sample : setup_test - cd tmp\test -- cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS -- cl /c /EHsc ..\..\test\main.cxx -+ cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS /I ..\..\test\include -+ cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include - link $(LFLAGS) calc.obj /out:calc.dll /DLL - link $(LFLAGS) main.obj calc.lib /out:tester.exe - tester.exe -@@ -77,15 +77,34 @@ test_wrapper : build_and_check_test_sample - rmdir /q /s tmp_bin - cd .. - --test_relocate: build_and_check_test_sample -+test_relocate_exe: build_and_check_test_sample - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe tester.exe --deploy --full -- relocate.exe tester.exe --export --full -+ relocate.exe --pe tester.exe --deploy --full -+ relocate.exe --pe tester.exe --export --full - tester.exe -+ move ..\calc.dll calc.dll -+ cd ../.. - --test: test_wrapper test_relocate -+test_relocate_dll: build_and_check_test_sample -+ cd tmp/test -+ -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe -+ cd .. -+ mkdir tmp_bin -+ mkdir tmp_lib -+ move test\calc.dll tmp_bin\calc.dll -+ move test\calc.lib tmp_lib\calc.lib -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ cd test -+ del tester.exe -+ link main.obj ..\tmp_lib\calc.lib /out:tester.exe -+ .\tester.exe -+ -+test_and_cleanup: test clean-test -+ -+ -+test: test_wrapper test_relocate_exe test_relocate_dll - - - clean : clean-test clean-cl -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 770d786..9f8aaa0 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -37,7 +37,7 @@ int redefinedArgCheck(const std::map &args, const char - int checkArgumentPresence(const std::map &args, const char * val, bool required = true) - { - if (args.find(val) == args.end()) { -- std::cerr << "Warning! Argument (" << val << ") not present"; -+ std::cerr << "Warning! Argument (" << val << ") not present\n"; - if (required) { - return 0; - } -@@ -58,19 +58,12 @@ std::map ParseRelocate(const char ** args, int argc) { - } - opts.insert(std::pair("pe", args[++i])); - } -- else if (endswith((std::string)args[i], ".dll")) { -- if(redefinedArgCheck(opts, "pe", "pe")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("pe", args[i])); -- } -- else if (endswith((std::string)args[i], ".exe")) { -- if(redefinedArgCheck(opts, "pe", "pe")) { -+ else if (!strcmp(args[i], "--coff")) { -+ if(redefinedArgCheck(opts, "coff", "--coff")) { - opts.clear(); - return opts; - } -- opts.insert(std::pair("pe", args[i])); -+ opts.insert(std::pair("coff", args[++i])); - } - else if (!strcmp(args[i], "--full")) { - if(redefinedArgCheck(opts, "full", "--full")) { -@@ -99,15 +92,20 @@ std::map ParseRelocate(const char ** args, int argc) { - } - opts.insert(std::pair("cmd", "deploy")); - } -+ else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { -+ opts.insert(std::pair("debug", "on")); -+ } -+ else if (!strcmp(args[i], "--verify")) { -+ opts.insert(std::pair("verify", "on")); -+ } -+ else if (!strcmp(args[i], "--report")) { -+ opts.insert(std::pair("report", "report")); -+ } - else { - // Unknown argument, warn the user it will not be used -- std::cerr << "Unknown argument :" << args[i] << "will be ignored\n"; -+ std::cerr << "Unknown argument: " << args[i] << " will be ignored\n"; - } - } -- if(!checkArgumentPresence(opts, "pe")) { -- opts.clear(); -- return opts; -- } - return opts; - } - -@@ -165,29 +163,37 @@ bool print_help() - std::cout << " In this case, the CLI of this wrapper is identical to cl|ifx|link.\n"; - std::cout << " See https://learn.microsoft.com/en-us/cpp/build/reference/c-cpp-building-reference\n"; - std::cout << "\n"; -- std::cout << " cl.exe /c foo.c"; -+ std::cout << " cl.exe /c foo.c\n"; - std::cout << "\n"; - std::cout << " To preform relocation, invoke the 'relocate' symlink to this file:\n"; - std::cout << "\n"; - std::cout << " Options:\n"; -- std::cout << " [--pe] = PE file to be relocated\n"; -+ std::cout << " --pe = PE (dll/exe) file to be relocated\n"; -+ std::cout << " [--coff ] = COFF (import library) file to be relocated\n"; -+ std::cout << " If relocating an exe, this is not required.\n"; - std::cout << " --full = Relocate dynamic references inside\n"; -- std::cout << " the dll in addition to re-generating\n"; -+ std::cout << " the pe in addition to re-generating\n"; - std::cout << " the import library\n"; - std::cout << " Note: this is assumed to be true if\n"; - std::cout << " relocating an executable.\n"; - std::cout << " If an executable is relocated, no import\n"; - std::cout << " library operations are performed.\n"; -+ std::cout << " When relocating a DLL, the import library for\n"; -+ std::cout << " said library is regenerated and the old imp lib\n"; -+ std::cout << " replaced.\n"; - std::cout << " --export|--deploy = Mutually exclusive command modifier.\n"; - std::cout << " Instructs relocate to either prepare the\n"; - std::cout << " dynamic library for exporting to build cache\n"; - std::cout << " or for extraction from bc onto new host system\n"; - std::cout << " --report = Report information about the parsed PE/Coff files\n"; -+ std::cout << " --debug|-d = Debug relocate run\n"; -+ std::cout << " --verify = Validates that the given file 'pe' is an import library\n"; - std::cout << "\n"; - std::cout << " To report on PE/COFF files, invoke the 'reporter' symlink to this executable or use the --report flag when invoking 'relocate'"; - std::cout << "\n"; - std::cout << " Options:\n"; - std::cout << " = Path to any PE or COFF file\n"; -+ std::cout << " To debug any flavor of this wrapper, set the environment variable SPACK_DEBUG_WRAPPER to any value in the wrapper context.\n"; - std::cout << "\n"; - return true; - } -@@ -204,4 +210,3 @@ bool CheckAndPrintHelp(const char ** arg, int argc) - return false; - - } -- -diff --git a/src/execute.cxx b/src/execute.cxx -index b89fff3..77d49a6 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -72,6 +72,7 @@ void ExecuteCommand::SetupExecute() - */ - int ExecuteCommand::CreateChildPipes() - { -+ // Create stdout pipes - SECURITY_ATTRIBUTES saAttr; - // Set the bInheritHandle flag so pipe handles are inherited. - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); -@@ -80,8 +81,21 @@ int ExecuteCommand::CreateChildPipes() - this->saAttr = saAttr; - if( !CreatePipe(&this->ChildStdOut_Rd, &this->ChildStdOut_Wd, &saAttr, 0) ) - return 0; -- if ( !SetHandleInformation(ChildStdOut_Rd, HANDLE_FLAG_INHERIT, 0) ) -+ if ( !SetHandleInformation(this->ChildStdOut_Rd, HANDLE_FLAG_INHERIT, 0) ) - return 0; -+ -+ // create stderr pipes -+ SECURITY_ATTRIBUTES saAttrErr; -+ // Set the bInheritHandle flag so pipe handles are inherited. -+ saAttrErr.nLength = sizeof(SECURITY_ATTRIBUTES); -+ saAttrErr.bInheritHandle = TRUE; -+ saAttrErr.lpSecurityDescriptor = NULL; -+ this->saAttrErr = saAttrErr; -+ if( !CreatePipe(&this->ChildStdErr_Rd, &this->ChildStdErr_Wd, &saAttrErr, 0) ) -+ return 0; -+ if ( !SetHandleInformation(this->ChildStdErr_Rd, HANDLE_FLAG_INHERIT, 0) ) -+ return 0; -+ - return 1; - } - -@@ -92,6 +106,7 @@ int ExecuteCommand::CreateChildPipes() - bool ExecuteCommand::ExecuteToolChainChild() - { - LPVOID lpMsgBuf; -+ debug("Executing Command: " + this->ComposeCLI()); - const std::wstring c_commandLine = ConvertAnsiToWide(this->ComposeCLI()); - wchar_t * nc_commandLine = _wcsdup(c_commandLine.c_str()); - if(! CreateProcessW( -@@ -118,6 +133,8 @@ bool ExecuteCommand::ExecuteToolChainChild() - (LPTSTR) &lpMsgBuf, - 0, NULL - ); -+ std::cerr << "Failed to initiate child process from: " << ConvertWideToANSI(nc_commandLine) << " "; -+ std::cerr << "With error: "; - std::cerr << (char*)lpMsgBuf << "\n"; - free(nc_commandLine); - this->cpw_initalization_failure = true; -@@ -132,6 +149,38 @@ bool ExecuteCommand::ExecuteToolChainChild() - return true; - } - -+ -+/* -+ * Reads for the member variable holding a pipe to the wrapped processes' -+ * STDERR and writes either to this processes' STDERR or a file, depending on -+ * how the process wrapper is configured -+ */ -+int ExecuteCommand::PipeChildToStdErr() -+{ -+ DWORD dwRead, dwWritten; -+ CHAR chBuf[BUFSIZE]; -+ BOOL bSuccess = TRUE; -+ HANDLE hParentOut; -+ if (this->write_to_file) { -+ hParentOut = this->fileout; -+ } -+ else { -+ hParentOut = GetStdHandle(STD_ERROR_HANDLE); -+ } -+ -+ for (;;) -+ { -+ bSuccess = ReadFile( this->ChildStdErr_Rd, chBuf, BUFSIZE, &dwRead, NULL); -+ if( ! bSuccess || dwRead == 0 ) break; -+ -+ bSuccess = WriteFile(hParentOut, chBuf, -+ dwRead, &dwWritten, NULL); -+ if (! bSuccess ) break; -+ } -+ return !bSuccess; -+} -+ -+ - /* - * Reads for the member variable holding a pipe to the wrapped processes' - * STDOUT and writes either to this processes' STDOUT or a file, depending on -@@ -230,9 +279,10 @@ bool ExecuteCommand::Execute(const std::string &filename) - NULL - ); - } -- int ret_code = this->ExecuteToolChainChild(); -+ bool ret_code = this->ExecuteToolChainChild(); - if (ret_code) { - this->child_out_future = std::async(std::launch::async, &ExecuteCommand::PipeChildToStdout, this); -+ this->child_err_future = std::async(std::launch::async, &ExecuteCommand::PipeChildToStdErr, this); - this->exit_code_future = std::async(std::launch::async, &ExecuteCommand::ReportExitCode, this); - } - return ret_code; -@@ -246,5 +296,7 @@ int ExecuteCommand::Join() - { - if(!this->child_out_future.get()) - return -999; -+ if(!this->child_err_future.get()) -+ return -999; - return this->exit_code_future.get(); - } -diff --git a/src/execute.h b/src/execute.h -index 8449517..94c5a74 100644 ---- a/src/execute.h -+++ b/src/execute.h -@@ -37,6 +37,7 @@ private: - void SetupExecute(); - bool ExecuteToolChainChild(); - int PipeChildToStdout(); -+ int PipeChildToStdErr(); - int CreateChildPipes(); - int CleanupHandles(); - int ReportExitCode(); -@@ -44,15 +45,21 @@ private: - // pipe from child process stdout - // to parent std out or file - std::future child_out_future; -+ // Holds the exit code of the pipe -+ // from child to parent stderr -+ std::future child_err_future; - // Holds the exit code of the - // command wrapped by this class - std::future exit_code_future; - std::string ComposeCLI(); - HANDLE ChildStdOut_Rd; - HANDLE ChildStdOut_Wd; -+ HANDLE ChildStdErr_Rd; -+ HANDLE ChildStdErr_Wd; - PROCESS_INFORMATION procInfo; - STARTUPINFOW startInfo; - SECURITY_ATTRIBUTES saAttr; -+ SECURITY_ATTRIBUTES saAttrErr; - HANDLE fileout = INVALID_HANDLE_VALUE; - bool write_to_file; - bool cpw_initalization_failure = false; -diff --git a/src/ld.cxx b/src/ld.cxx -index c4eb315..7cbe9fb 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -33,13 +33,16 @@ int LdInvocation::InvokeToolchain() - // We're creating a dll, we need to create an appropriate import lib - if(!link_run.IsExeLink()) { - std::string basename = link_run.get_name(); -- std::string imp_lib_name = strip(basename, ".dll") + ".lib"; -+ std::string imp_lib_name = link_run.get_implib_name(); - std::string dll_name = link_run.get_mangled_out(); -- std::string abs_out_imp_lib_name = basename + ".dll-abs.lib"; -+ std::string abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; -+ std::string def_file = link_run.get_def_file(); -+ std::string def_line = "-def"; -+ def_line += !def_file.empty() ? ":" + def_file : ""; - // create command line to generate new import lib - this->rpath_executor = ExecuteCommand("lib.exe", this->ComposeCommandLists( - { -- {"-def", "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, -+ {def_line, "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, - this->obj_args, - this->lib_args, - this->lib_dir_args, -@@ -52,13 +55,25 @@ int LdInvocation::InvokeToolchain() - } - CoffReaderWriter cr(abs_out_imp_lib_name); - CoffParser coff(&cr); -- coff.Parse(); -+ if(!coff.Parse()) { -+ debug("Failed to parse COFF file: " + abs_out_imp_lib_name); -+ return -9; -+ } - if(!coff.NormalizeName(dll_name)){ -+ debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); - return -9; - } -- std::remove(imp_lib_name.c_str()); -- std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); -- -+ debug("Renaming library from " + abs_out_imp_lib_name + " to " + imp_lib_name); -+ int remove_exitcode = std::remove(imp_lib_name.c_str()); -+ if(remove_exitcode) { -+ debug("Failed to remove original import library with exit code: " + remove_exitcode); -+ return -10; -+ } -+ int rename_exitcode = std::rename(abs_out_imp_lib_name.c_str(), imp_lib_name.c_str()); -+ if(rename_exitcode) { -+ debug("Failed to rename temporary import library with exit code: " + rename_exitcode); -+ return -11; -+ } - } - return ret_code; - } -diff --git a/src/main.cxx b/src/main.cxx -index a1a6617..829847f 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -25,16 +25,61 @@ int main(int argc, const char* argv[]) { - bool deploy = !(patch_args.find("cmd") == patch_args.end()) - && patch_args.at("cmd") == "deploy"; - bool report = !(patch_args.find("report") == patch_args.end()); -- bool is_exe = endswith(patch_args.at("pe"), ".exe"); -+ bool has_pe = !(patch_args.find("pe") == patch_args.end()); -+ bool is_exe = has_pe ? endswith(patch_args.at("pe"), ".exe") : false; -+ bool debug = !(patch_args.find("debug") == patch_args.end()); -+ bool is_validate = !(patch_args.find("verify") == patch_args.end()); -+ bool has_coff = !(patch_args.find("coff") == patch_args.end()); - // Without full with a DLL we re-produce the import lib from the - // relocated DLL, but with an EXE there is nothing to do -+ if(!has_coff && !has_pe) { -+ std::cout << "No binaries to operate on... nothing to do\n"; -+ return -1; -+ } - if (is_exe && !full) - { -- std::cerr << "Executable file provided but --full not specified, nothing to do...\n"; -+ std::cout << "Executable file provided but --full not specified, nothing to do...\n"; -+ return -1; -+ } -+ // The only scenario its ok to have a dll/exe and no coff is when we're creating a cache -+ // entry -+ if (!is_exe && !has_coff && !deploy) { -+ std::cout << "Attempting to relocate DLL without coff file, please provide a coff file.\n"; -+ return -1; -+ } -+ if (is_validate && !has_coff) { -+ std::cout << "Attempting to validate without a coff file, nothing to validate\n"; -+ return -1; -+ } -+ if (report && !has_coff) { -+ std::cout << "Attempting to report without a binary, nothing to report...\n"; -+ return -1; -+ } -+ if (!(is_validate || report) && !has_pe) { -+ std:: cout << "Attempting to perform relocation without a PE file, please provide one.\n"; -+ return -1; -+ } -+ if (is_validate) { -+ return CoffParser::Validate(patch_args.at("coff")); -+ } -+ if (report) { -+ CoffReaderWriter cr(patch_args.at("coff")); -+ CoffParser coff(&cr); -+ if(!reportCoff(coff)) { -+ return 1; -+ } - return 0; - } -- LibRename rpath_lib(patch_args.at("pe"), full, deploy, true, report); -- if(!rpath_lib.ExecuteRename()){ -+ DEBUG = debug; -+ std::unique_ptr rpath_lib; -+ if (has_coff) -+ { -+ rpath_lib = std::make_unique(patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); -+ } -+ else { -+ rpath_lib = std::make_unique(patch_args.at("pe"), full, deploy, true); -+ } -+ if(!rpath_lib->ExecuteRename()){ - std::cerr << "Library rename failed\n"; - return 9; - } -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index b71a167..b4fb3a8 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -6,6 +6,8 @@ - #include "toolchain.h" - - #include -+#include -+ - - ToolChainInvocation::ToolChainInvocation(std::string command, char const* const* cli) : - command(command) -@@ -42,10 +44,12 @@ int ToolChainInvocation::InvokeToolchain() { - this->executor = ExecuteCommand( this->command, - commandLine - ); -+ debug("Setting up executor for " + std::string(typeid(*this).name()) + "toolchain"); -+ debug("Toolchain: " + this->command); - // Run first pass of command as requested by caller - int ret_code = this->executor.Execute(); - if(!ret_code) { -- std::cerr << "Unable to launch process \n"; -+ std::cerr << "Unable to launch toolchain process \n"; - return -9999; - } - return this->executor.Join(); -@@ -56,7 +60,7 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // Includes come first - for( char const* const* c = cli; *c; c++ ){ - std::string arg = std::string(*c); -- if ( startswith(arg, "/I") ) { -+ if ( startswith(arg, "/I") || startswith(arg, "-I") ) { - // We have an include arg - // can have an optional space - // check if there are characters after -@@ -64,10 +68,12 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // argument to be the include - if (arg.size() > 2) - this->include_args.push_back(arg); -- else -+ else { -+ this->include_args.push_back(arg); - this->include_args.push_back(std::string(*(++c))); -+ } - } -- else if( endswith(arg, ".lib") ) -+ else if( endswith(arg, ".lib") && (arg.find("implib:") == std::string::npos)) - // Lib args are just libraries - // provided like system32.lib on the - // command line. -@@ -102,6 +108,8 @@ StrList ToolChainInvocation::ComposeCommandLists(std::vector command_ar - StrList commandLine; - for(auto arg_list : command_args) - { -+ // Ensure arguments are appropriately quoted -+ quoteList(arg_list); - commandLine.insert(commandLine.end(), arg_list.begin(), arg_list.end()); - } - return commandLine; -diff --git a/src/utils.cxx b/src/utils.cxx -index d96876e..3300102 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -162,8 +162,7 @@ void StripPathAndExe(std::string &command) { - - void StripExe(std::string &command) { - // Normalize command to lowercase to avoid parsing issues -- std::transform(command.begin(), command.end(), command.begin(), -- [](unsigned char c){ return std::tolower(c); }); -+ lower(command); - std::string::size_type loc = command.rfind(".exe"); - if ( std::string::npos != loc && loc + 4 == command.length() ) - command.erase(loc); -@@ -173,6 +172,29 @@ void StripPath(std::string &command) { - command.erase(0, command.find_last_of("\\/") + 1); - } - -+/** -+ * Converts a string to lowercase -+ * -+ * \arg str - string to be made lowercase -+ */ -+void lower(std::string &str) { -+ std::transform(str.begin(), str.end(), str.begin(), -+ [](unsigned char c){ return std::tolower(c); }); -+} -+ -+ -+std::string quoteAsNeeded(std::string &str) { -+ if (str.find_first_of(" &<>|()") != std::string::npos) { -+ // There are spaces or special characters in string, quote it -+ return "\"" + str + "\""; -+ } -+ return str; -+} -+ -+ -+void quoteList(StrList &args) { -+ std::transform(args.begin(), args.end(), args.begin(), quoteAsNeeded); -+} - - /** - * Given an environment variable name -@@ -235,7 +257,7 @@ std::string basename(const std::string &file) - { - std:size_t last_path = file.find_last_of("\\")+1; - if (last_path == std::string::npos) { -- return std::string(); -+ return file; - } - return file.substr(last_path); - } -@@ -276,6 +298,16 @@ DWORD RvaToFileOffset(PIMAGE_SECTION_HEADER §ion_header, DWORD number_of_sec - } - - -+void debug(std::string dbgStmt) { -+ if (DEBUG || getenv("SPACK_DEBUG_WRAPPER")) { -+ std::cout << "DEBUG: " << dbgStmt << "\n"; -+ } -+} -+ -+void debug(char * dbgStmt, int len) { -+ debug(std::string(dbgStmt, len)); -+} -+ - std::string reportLastError() - { - DWORD error = GetLastError(); -@@ -412,6 +444,19 @@ DWORD ToLittleEndian(DWORD val) - return little_endian_val; - } - -+int get_slash_name_length(char *slash_name) -+{ -+ if(slash_name == nullptr) { -+ return 0; -+ } -+ int len = 0; -+ // Maximum length for a given name in the PE/COFF format is 143 chars -+ while(slash_name[len] != '/' && len < 144) { -+ ++len; -+ } -+ return len; -+} -+ - char * findstr(char *search_str, const char * substr, int size) - { - char * search = search_str; -diff --git a/src/utils.h b/src/utils.h -index 6dc2222..a5376bd 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -59,6 +59,9 @@ StrList split(const std::string &s, const std::string &delim); - //Strips substr off the RHS of the larger string - std::string strip(const std::string& s, const std::string &substr); - -+//Strips substr of LHS of the larger string -+std::string lstrip(const std::string& s, const std::string &substr); -+ - // Joins vector of strings by join character - std::string join(const StrList &strs, const std::string &join_char = " "); - -@@ -78,10 +81,22 @@ void StripExe(std::string &command); - // resulting in a parentless, non exe extensioned path - void StripPathAndExe(std::string &command); - -+// Make str lowercase -+void lower(std::string &str); -+ -+// Given a string containing something terminated by a -+// forward slash, get the length of the substr terminated -+// by / -+int get_slash_name_length(char *slash_name); -+ - // Implementation of strstr but serch is bounded at size and - // does not terminate on the first read nullptr - char * findstr(char * search_str, const char * substr, int size); - -+// Adds quote to relevent strings in a list of strings -+// Strings to be quoted contain: spaces, or any of &<>|() -+void quoteList(StrList &args); -+ - // FS/Path helpers // - - // Returns current working directory -@@ -109,6 +124,12 @@ std::string reportLastError(); - // files in big endian format - DWORD ToLittleEndian(DWORD val); - -+// Operating Utils // -+ -+void debug(std::string dbgStmt); -+ -+void debug(char * dbgStmt, int len); -+ - /** - * Library Searching utility class - * Collection of heuristics and logic surrounding library -@@ -132,3 +153,5 @@ public: - std::string FindLibrary(const std::string &lib_name, const std::string &lib_path); - void EvalSearchPaths(); - }; -+ -+static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index 6eb7863..b2cad80 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -160,24 +160,45 @@ LinkerInvocation::LinkerInvocation(const StrList &linkLine) - void LinkerInvocation::Parse() - { - for (auto token = this->tokens.begin(); token != this->tokens.end(); ++token) { -- if (endswith(*token, ".lib")) { -+ std::string normalToken = *token; -+ lower(normalToken); -+ // implib specifies the eventuall import libraries name -+ // and thus will contain a ".lib" extension, which -+ // the next check will process as a library argument -+ if (normalToken.find("implib:") != std::string::npos) { -+ // If there was nothing after the ":", the -+ // previous link command would have failed -+ // and : is not a legal character in a name -+ // guarantees this split command produces a vec of -+ // len 2 -+ StrList implibLine = split(*token, ":"); -+ this->implibname = implibLine[1]; -+ } -+ else if (endswith(normalToken, ".lib")) { - this->libs.push_back(*token); - } -- else if (*token == "/dll" || *token == "/DLL") { -+ else if (normalToken == "/dll" || normalToken == "-dll") { - this->is_exe = false; - } -- else if (startswith(*token, "-out") || startswith(*token, "/out")) { -+ else if (startswith(normalToken, "-out") || startswith(normalToken, "/out")) { - this->output = split(*token, ":")[1]; - } -- else if (endswith(*token, ".obj")) { -+ else if (endswith(normalToken, ".obj")) { - this->objs.push_back(*token); - } -+ else if (normalToken.find("def:") != std::string::npos) { -+ StrList defLine = split(*token, ":"); -+ this->def_file = defLine[1]; -+ } - } - std::string ext = this->is_exe ? ".exe" : ".dll"; - if (this->output.empty()){ - this->output = strip(this->objs.front(), ".obj") + ext; - } - this->name = strip(this->output, ext); -+ if (this->implibname.empty()) { -+ this->implibname = this->name + ".lib"; -+ } - } - - std::string LinkerInvocation::get_name() -@@ -185,6 +206,16 @@ std::string LinkerInvocation::get_name() - return this->name; - } - -+std::string LinkerInvocation::get_implib_name() -+{ -+ return this->implibname; -+} -+ -+std::string LinkerInvocation::get_def_file() -+{ -+ return this->def_file; -+} -+ - std::string LinkerInvocation::get_out() - { - return this->output; -@@ -330,7 +361,10 @@ bool CoffParser::Parse() - std::streampos offset = this->coffStream->tell(); - this->coffStream->ReadHeader(header); - this->coffStream->ReadMember(header, member); -- this->ParseData(header, member); -+ if (!this->ParseData(header, member)) { -+ this->verified = true; -+ return false; -+ } - coff_entry entry; - entry.header = header; - entry.member = member; -@@ -347,6 +381,22 @@ bool CoffParser::Parse() - return true; - } - -+int CoffParser::Verify() -+{ -+ bool parseStatus = this->Parse(); -+ if(!parseStatus && !this->verified) { -+ // actual error in parsing the library -+ return 2; -+ } -+ else if(!parseStatus && this->verified) { -+ // library is valid, it's just a static -+ // lib, not an import -+ return 1; -+ } -+ // otherwise, successful, it's an import lib -+ return 0; -+} -+ - /** - * Parses a member section in the form of a short format import section - * based on the COFF structure scheme -@@ -468,6 +518,17 @@ void CoffParser::ParseSecondLinkerMember(coff_member *member) - member->second_link = sl; - } - -+ -+namespace { -+ bool nameCheck(BYTE* name) -+ { -+ int nameLen = get_slash_name_length((char*)name); -+ if(findstr((char*)name, ".obj", nameLen)) { -+ return false; -+ } -+ return true; -+ } -+} - /** - * Drive the parsing of the "data" section of an import library member - * -@@ -482,7 +543,7 @@ void CoffParser::ParseSecondLinkerMember(coff_member *member) - * \param header A pointer to the archive member header corresponding to the member being parsed - * \param member A pointer to the member data being parsed - */ --void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member) -+bool CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member) - { - IMPORT_OBJECT_HEADER * p_imp_header = (IMPORT_OBJECT_HEADER *)member->data; - if((p_imp_header->Sig1 == IMAGE_FILE_MACHINE_UNKNOWN) && (p_imp_header->Sig2 == IMPORT_OBJECT_HDR_SIG2)) { -@@ -490,6 +551,9 @@ void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *mem - this->ParseShortImport(member); - } - else if (!strncmp((char*)header->Name, IMAGE_ARCHIVE_LINKER_MEMBER, 16)) { -+ if(!nameCheck(header->Name)){ -+ return false; -+ } - if (!this->coff.read_first_linker) { - this->ParseFirstLinkerMember(member); - this->coff.read_first_linker = true; -@@ -499,13 +563,36 @@ void CoffParser::ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *mem - } - } - else if (!strncmp((char*)header->Name, IMAGE_ARCHIVE_LONGNAMES_MEMBER, 16)) { -- // Long names member doesn't provide us anything useful to parse -- // at this stage -- return; -+ // Check the long names member for values, if so, check the extension has a dll -+ if (!this->ValidateLongName(member, atoi((char*)header->Size))) { -+ return false; -+ } -+ member->is_longname = true; - } - else { -+ if(!nameCheck(header->Name)) { -+ return false; -+ } - this->ParseFullImport(member); - } -+ return true; -+} -+ -+bool CoffParser::ValidateLongName(coff_member* member, int size) -+{ -+ if (!member->data) { -+ // If we have no member, by virtue of correctly processing -+ // the header to get to this point -+ // we have a valid header -+ return true; -+ } -+ // If a name has an object file, this is not an import -+ // member -+ char * objRes = findstr(member->data, ".obj", size); -+ if (!objRes) { -+ return true; -+ } -+ return false; - } - - -@@ -553,11 +640,7 @@ void CoffParser::NormalizeSectionNames(const std::string &name, char* section, c - char * new_name = new char[name_len]; - strncpy(new_name, section_search_start, name_len); - replace_special_characters(new_name, name_len); -- this->coffStream->seek(0); -- this->coffStream->seek(section_data_start_offset + offset); -- // Reduce name len by one to prevent writing out the null terminator -- // that is part of the new_name -- this->coffStream->write(new_name, name_len); -+ this->writeRename(new_name, name_len, section_data_start_offset + offset); - delete new_name; - section_search_start += name_len+1; - offset = section_search_start - section; -@@ -565,6 +648,18 @@ void CoffParser::NormalizeSectionNames(const std::string &name, char* section, c - } - } - -+void CoffParser::writeRename(char* name, const int size, const int loc) -+{ -+ this->coffStream->seek(0); -+ this->coffStream->seek(loc); -+ this->coffStream->write(name, size); -+} -+ -+bool CoffParser::validateName(char* old_name, std::string new_name) -+{ -+ return !strcmp(old_name, new_name.c_str()); -+} -+ - /** - * Normalizes mangled DLL names that represent absolute paths in COFF - * binary files -@@ -609,18 +704,12 @@ bool CoffParser::NormalizeName(std::string &name) - // We know it exists at this point due to the success of the conditional above - char* long_name = new char[long_name_len+1]; - strncpy(long_name, this->coff.members[2].member->data+longname_offset, long_name_len+1); -- // Ensure Dll name is the one we're looking to perform the rename for -- if (!strcmp(name.c_str(), long_name) && !long_name_renamed) { -+ if (this->validateName(long_name, name) && !long_name_renamed) { - // If so, unmangle it - replace_special_characters(long_name, long_name_len+1); - // offset of actual longname member - int offset = std::streamoff(this->coff.members[2].offset); -- this->coffStream->seek(0); -- // Seek to longname header -- this->coffStream->seek(offset); -- // Seek to offset within longname member for a given import name -- this->coffStream->seek(sizeof(IMAGE_ARCHIVE_MEMBER_HEADER) + longname_offset, std::ios_base::cur); -- this->coffStream->write(long_name, long_name_len+1); -+ this->writeRename(long_name, long_name_len+1, offset + sizeof(IMAGE_ARCHIVE_MEMBER_HEADER) + longname_offset); - long_name_renamed = true; - } - delete long_name; -@@ -636,7 +725,7 @@ bool CoffParser::NormalizeName(std::string &name) - strcpy(new_name, mem.member->short_member->short_dll); - replace_special_characters(new_name, name_len); - // ensure it's the name we're looking to rename -- if(!strcmp(name.c_str(), mem.member->short_member->short_dll)) { -+ if(this->validateName(mem.member->short_member->short_dll, name)) { - // Member offset in file - int offset = std::streamoff(mem.offset); - // Member header offset -@@ -647,9 +736,7 @@ bool CoffParser::NormalizeName(std::string &name) - // Next is the symbol name, which is a null terminated string - // +1 to preserve the null terminator in the coff member - offset += strlen(mem.member->short_member->short_name) + 1; -- this->coffStream->seek(0); -- this->coffStream->seek(offset); -- this->coffStream->write(new_name, strlen(new_name)); -+ this->writeRename(new_name, strlen(new_name), offset); - } - delete new_name; - } -@@ -689,9 +776,7 @@ bool CoffParser::NormalizeName(std::string &name) - char * new_no_ext_name = new char[name_len]; - strncpy(new_no_ext_name, string_table_start, name_len); - replace_special_characters(new_no_ext_name, name_len); -- this->coffStream->seek(0); -- this->coffStream->seek(relative_string_table_start_offset + offset); -- this->coffStream->write(new_no_ext_name, name_len); -+ this->writeRename(new_no_ext_name, name_len, relative_string_table_start_offset + offset); - delete new_no_ext_name; - } - } -@@ -750,19 +835,27 @@ void CoffParser::ReportShortImportMember(short_import_member *si) - } - - -+void CoffParser::ReportLongName(char * data) -+{ -+ std::cout << "DLL: " << data << "\n"; -+} -+ - void CoffParser::Report() - { - for (auto mem: this->coff.members) { -- reportArchiveHeader(mem.header); -- if(mem.member->long_member) { -- this->ReportLongImportMember(mem.member->long_member); -- } -- else if(mem.member->short_member) { -- this->ReportShortImportMember(mem.member->short_member); -+ if(mem.member->is_longname) { -+ this->ReportLongName(mem.member->data); - } - } - } - -+int CoffParser::Validate(std::string &coff) -+{ -+ CoffReaderWriter cr(coff); -+ CoffParser coffp(&cr); -+ return coffp.Verify(); -+} -+ - /** - * Reports information about parsed coff file - * -@@ -978,12 +1071,17 @@ bool LibRename::FindDllAndRename(HANDLE &pe_in) - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace, bool report) -+LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace) - : replace(replace), full(full), pe(pe), deploy(deploy) -+{} -+ -+LibRename::LibRename(std::string pe, std::string coff, bool full, bool deploy, bool replace) -+: replace(replace), full(full), pe(pe), deploy(deploy), coff(coff) - { -- this->name = stem(this->pe); - this->is_exe = endswith(this->pe, ".exe"); -- this->def_file = this->name + ".def"; -+ std::string coff_path = stem(this->coff); -+ this->tmp_def_file = coff_path + "-tmp.def"; -+ this->def_file = coff_path + ".def"; - this->def_executor = ExecuteCommand("dumpbin.exe", {this->ComputeDefLine()}); - this->lib_executor = ExecuteCommand("lib.exe", {this->ComputeRenameLink()}); - } -@@ -992,11 +1090,11 @@ LibRename::LibRename(std::string pe, bool full, bool deploy, bool replace, bool - * Creates the line to be provided to dumpbin.exe to produce the exports of a given - * dll in the case where we do not have access to the original link line - * -- * Produces something like `/EXPORTS ` - */ - std::string LibRename::ComputeDefLine() - { -- return "/EXPORTS " + this->pe; -+ return "/NOLOGO /EXPORTS " + this->coff; - } - - /** -@@ -1005,9 +1103,51 @@ std::string LibRename::ComputeDefLine() - * - * Returns the return code of the Def file computation operation - */ --int LibRename::ComputeDefFile() -+bool LibRename::ComputeDefFile() - { -- return this->def_executor.Execute(this->def_file); -+ this->def_executor.Execute(this->tmp_def_file); -+ int res = this->def_executor.Join(); -+ if(res) { -+ return false; -+ } -+ // Need to process the produced def file because it's wrong -+ // Open input file -+ std::ifstream inputFile(this->tmp_def_file); -+ if (!inputFile.is_open()) { -+ std::cerr << "Error: Could not open input file " << tmp_def_file << std::endl; -+ return false; -+ } -+ -+ // Open output file -+ std::ofstream outputFile(this->def_file); -+ if (!outputFile.is_open()) { -+ std::cerr << "Error: Could not open output file " << this->def_file << std::endl; -+ return false; -+ } -+ -+ // Write the standard .def file header -+ // You might want to get the DLL name dynamically from the input filename or dumpbin output -+ outputFile << "EXPORTS\n"; -+ -+ std::string line; -+ // First 8 lines are templated output -+ // skip them -+ for (int i = 0; i < 8; ++i) { // Adjust this number if the header changes across dumpbin versions -+ if (!std::getline(inputFile, line)) break; -+ } -+ while (std::getline(inputFile, line)) { -+ if (line.empty()) { -+ continue; -+ } -+ else if(line.find("Summary") != std::string::npos) { // Skip header in export block if still present -+ break; -+ } -+ outputFile << " " << lstrip(line, " ") << std::endl; -+ } -+ inputFile.close(); -+ outputFile.close(); -+ std::remove(this->tmp_def_file.c_str()); -+ return true; - } - - /** -@@ -1037,19 +1177,23 @@ bool LibRename::ExecuteRename() - // recompute the .def and .lib for dlls - // exes do not typically have import libs so we don't handle - // that case -- if(!this->deploy && !this->is_exe){ -+ // We do not bother with defs for things that don't have -+ // import libraries -+ if(!this->deploy && !this->coff.empty()){ - // Extract DLL -- if(this->ComputeDefFile()) { -+ if(!this->ComputeDefFile()) { -+ debug("Failed to compute def file"); - return false; - } - if(!this->ExecuteLibRename()) { -+ debug("Failed to create and rename import lib"); - return false; - } - } - if (this->full) { - if(!this->ExecutePERename()) { - std::cerr << "Unable to execute rename of " -- "referenced components in PE file: " << this->name << "\n"; -+ "referenced components in PE file: " << this->pe << "\n"; - return false; - } - } -@@ -1069,20 +1213,24 @@ bool LibRename::ExecuteLibRename() - this->lib_executor.Execute(); - int ret_code = this->lib_executor.Join(); - if(ret_code != 0) { -- std::cerr << "Lib Rename failed" << reportLastError() << "\n"; -+ std::cerr << "Lib Rename failed with exit code: " << ret_code << "\n"; - return false; - } -+ // replace former .lib with renamed .lib -+ std::remove(this->coff.c_str()); -+ std::rename(this->new_lib.c_str(), this->coff.c_str()); - // import library has been generated with - // mangled abs path to dll - - // unmangle it -- CoffReaderWriter cr(this->new_lib); -+ CoffReaderWriter cr(this->coff); - CoffParser coff(&cr); - if (!coff.Parse()) { - std::cerr << "Unable to parse generated import library {" << this->new_lib <<"}\n"; - return false; - } -- if(!coff.NormalizeName(mangle_name(this->pe) )) { -- std::cerr << "Unable to normalize name\n"; -+ std::string mangledName = mangle_name(this->pe); -+ if(!coff.NormalizeName(mangledName)) { -+ std::cerr << "Unable to normalize name: " << mangledName << "\n"; - return false; - } - return true; -@@ -1094,10 +1242,10 @@ bool LibRename::ExecuteLibRename() - */ - bool LibRename::ExecutePERename() - { -- LPCWSTR lib_name = ConvertAnsiToWide(this->pe).c_str(); -- HANDLE pe_handle = CreateFileW(lib_name, (GENERIC_READ|GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); -+ std::wstring pe_path = ConvertAnsiToWide(this->pe); -+ HANDLE pe_handle = CreateFileW(pe_path.c_str(), (GENERIC_READ|GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE){ -- std::cerr << "Unable to acquire file handle to "<< lib_name << ": " << reportLastError() << "\n"; -+ std::cerr << "Unable to acquire file handle to "<< pe_path.c_str() << ": " << reportLastError() << "\n"; - return false; - } - return this->FindDllAndRename(pe_handle); -@@ -1131,13 +1279,14 @@ std::string LibRename::ComputeRenameLink() - line += this->def_file + " "; - line += "-name:"; - line += mangle_name(this->pe) + " "; -- std::string name(stem(this->pe)); -+ std::string name(stem(this->coff)); - if (!this->replace){ - this->new_lib = name + ".abs-name.lib"; - } - else { -- this->new_lib = this->pe; -+ // Name must be different -+ this->new_lib = name+"-tmp.lib"; - } -- line += "-out:\""+ this->new_lib + "\"" + " " + this->pe; -+ line += "-out:\""+ this->new_lib + "\""; - return line; - } -diff --git a/src/winrpath.h b/src/winrpath.h -index d2657b5..e440982 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -92,8 +92,10 @@ typedef struct coff_member { - first_linker_member *first_link; - second_linker_member *second_link; - bool is_short; -+ bool is_longname; - coff_member() { - this->is_short = false; -+ this->is_longname = false; - this->first_link = NULL; - this->second_link = NULL; - this->short_member = NULL; -@@ -177,22 +179,28 @@ class CoffParser { - private: - CoffReaderWriter* coffStream; - coff coff; -- void ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member); -+ bool verified = false; -+ bool ParseData(PIMAGE_ARCHIVE_MEMBER_HEADER header, coff_member *member); - void ParseShortImport(coff_member *member); - void ParseFullImport(coff_member *member); - void ParseFirstLinkerMember(coff_member *member); - void ParseSecondLinkerMember(coff_member *member); - void ReportLongImportMember(long_import_member *li); - void ReportShortImportMember(short_import_member *si); -+ void ReportLongName(char * data); - void NormalizeLinkerMember(const std::string &name, const int &base_offset, const int &offset, const char * strings, const DWORD symbols); - void NormalizeSectionNames(const std::string &name, char* section, const DWORD §ion_data_start_offset, int data_size); -- -+ bool ValidateLongName(coff_member *member, int size); -+ void writeRename(char *name, const int size, const int loc); -+ bool validateName(char *old_name, std::string new_name); - public: - CoffParser(CoffReaderWriter * cr); - ~CoffParser() = default; - bool Parse(); - bool NormalizeName(std::string &name); - void Report(); -+ int Verify(); -+ static int Validate(std::string &coff); - }; - - class LinkerInvocation { -@@ -205,10 +213,14 @@ public: - std::string get_name(); - std::string get_out(); - std::string get_mangled_out(); -+ std::string get_implib_name(); -+ std::string get_def_file(); - private: - std::string line; - StrList tokens; - std::string name; -+ std::string implibname; -+ std::string def_file; - std::string output; - StrList libs; - StrList objs; -@@ -218,11 +230,12 @@ private: - - class LibRename { - public: -- LibRename(std::string pe, bool full, bool deploy, bool replace, bool report); -+ LibRename(std::string pe, std::string coff, bool full, bool deploy, bool replace); -+ LibRename(std::string pe, bool full, bool deploy, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -- int ComputeDefFile(); -+ bool ComputeDefFile(); - std::string ComputeRenameLink(); - std::string ComputeDefLine(); - -@@ -233,14 +246,14 @@ private: - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -- std::string name; -+ std::string coff; - std::string new_lib; - std::string def_file; -+ std::string tmp_def_file; - bool full; - bool deploy; - bool replace; - bool is_exe; -- bool report; - }; - - -diff --git a/test/calc.h b/test/calc.h -deleted file mode 100644 -index d7d3975..0000000 ---- a/test/calc.h -+++ /dev/null -@@ -1,14 +0,0 @@ --/** -- * Copyright Spack Project Developers. See COPYRIGHT file for details. -- * -- * SPDX-License-Identifier: (Apache-2.0 OR MIT) -- */ --#pragma once -- --#ifdef CALC_EXPORTS --#define CALC_API __declspec(dllexport) --#else --#define CALC_API __declspec(dllimport) --#endif -- --extern "C" CALC_API int add(int a, int b); diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch deleted file mode 100644 index 1889d490897..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/improve_def_arg_forwarding.patch +++ /dev/null @@ -1,113 +0,0 @@ -diff --git a/src/ld.cxx b/src/ld.cxx -index 8669273..7bdb209 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -44,8 +44,12 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; -- std::string def = "-def "; -- std::string piped_args = link_run.get_lib_link_args(); -+ std::string const def_file = link_run.get_def_file().empty() -+ ? " " -+ : ":" + link_run.get_def_file(); -+ std::string const def = "-def" + def_file; -+ -+ std::string const piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib - this->rpath_executor = - ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 1a96b5a..dd26c30 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -46,11 +46,10 @@ void LinkerInvocation::Parse() { - this->implibname_ = implib_line[1]; - } else if (endswith(normal_token, ".lib")) { - this->libs_.push_back(*token); -- } else if (normal_token == "/dll" || normal_token == "-dll") { -+ } else if (normal_token == "dll") { - this->is_exe_ = false; -- } else if (startswith(normal_token, "-out") || -- startswith(normal_token, "/out")) { -- this->output_ = split(*token, ":", 1)[1]; -+ } else if (startswith(normal_token, "out")) { -+ this->output_ = split(*token, ":")[1]; - } else if (endswith(normal_token, ".obj")) { - this->objs_.push_back(*token); - } else if (startswith(normal_token, "@") && -@@ -60,9 +59,11 @@ void LinkerInvocation::Parse() { - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits - this->rsp_file_ = *token; -+ } else if (startswith(normal_token, "def")) { -+ this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != - this->piped_args_.end()) { -- this->piped_args_.at(normal_token).emplace_back(normal_token); -+ this->piped_args_.at(normal_token).emplace_back(*token); - } - } - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index d195afe..a892094 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -36,9 +36,8 @@ class LinkerInvocation { - StrList objs_; - bool is_exe_; - std::map piped_args_ = { -- {"def", {}}, {"export", {}}, {"include", {}}, -- {"libpath", {}}, {"ltcg", {}}, {"machine", {}}, -- {"nodefaultlib", {}}, {"subsystem", {}}, {"verbose", {}}, -- {"wx", {}}, -+ {"export", {}}, {"include", {}}, {"libpath", {}}, -+ {"ltcg", {}}, {"machine", {}}, {"nodefaultlib", {}}, -+ {"subsystem", {}}, {"verbose", {}}, {"wx", {}}, - }; - }; -\ No newline at end of file -diff --git a/src/utils.cxx b/src/utils.cxx -index 49db7f4..f2a98e1 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -224,6 +224,14 @@ void lower(std::string& str) { - }); - } - -+/** -+ * Quotes str as needed -+ * If str has existing escaped quotes, or a space/reserved character -+ * Escape escaped quotes using an escaped backslash preceding the escaped -+ * quote. Escape reserved characters by quoting the entire string -+ * -+ * Return the escaped string -+ */ - std::string quoteAsNeeded(std::string& str) { - // Note: the ordering if these two conditionals is important - // If the second conditional is executed first, the first -@@ -448,8 +456,10 @@ bool isCommandArg(const std::string& arg, const std::string& command) { - void normalArg(std::string& arg) { - // first normalize capitalization - lower(arg); -+ // strip any leading/trailing quotes -+ arg = strip(lstrip(arg, "\""), "\""); - // strip leading / and - -- arg = strip(strip(arg, "-"), "/"); -+ arg = lstrip(lstrip(arg, "-"), "/"); - } - - std::string reportLastError() { -diff --git a/src/utils.h b/src/utils.h -index e2f5a80..e5a7ed3 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -132,6 +132,8 @@ char* findstr(char* search_str, const char* substr, size_t size); - // side effects on Windows - void quoteList(StrList& args); - -+std::string quoteAsNeeded(std::string& str); -+ - /// @brief Searches a sections of a string for a given regex using provided - /// options to control search behavior - /// @param searchDomain - string to be searched diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch deleted file mode 100644 index 3191e85c040..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/include_regex.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index 220be6f..f6311c6 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -12,6 +12,7 @@ - #include - #include - #include "shlwapi.h" -+#include - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch deleted file mode 100644 index d533fe9de68..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/long_name.patch +++ /dev/null @@ -1,264 +0,0 @@ -diff --git a/src/ld.cxx b/src/ld.cxx -index ff9bdde..890f364 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,7 @@ - #include "ld.h" - #include - #include -+#include - #include "coff_parser.h" - #include "coff_reader_writer.h" - #include "linker_invocation.h" -@@ -34,7 +35,14 @@ DWORD LdInvocation::InvokeToolchain() { - // We're creating a dll, we need to create an appropriate import lib - if (!link_run.IsExeLink()) { - std::string const imp_lib_name = link_run.get_implib_name(); -- std::string dll_name = link_run.get_mangled_out(); -+ std::string dll_name; -+ try { -+ dll_name = link_run.get_mangled_out(); -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Unable to mangle PE " << link_run.get_out() -+ << " name is too long\n"; -+ return ExitConditions::NORMALIZE_NAME_FAILURE; -+ } - std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; - std::string def = "-def "; - std::string piped_args = link_run.get_lib_link_args(); -diff --git a/src/main.cxx b/src/main.cxx -index 9458d17..26d940d 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -89,12 +89,19 @@ int main(int argc, const char* argv[]) { - } - DEBUG = debug; - std::unique_ptr rpath_lib; -- if (has_coff) { -- rpath_lib = std::make_unique( -- patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); -- } else { -- rpath_lib = std::make_unique(patch_args.at("pe"), full, -- deploy, true); -+ try { -+ if (has_coff) { -+ rpath_lib = std::make_unique(patch_args.at("pe"), -+ patch_args.at("coff"), -+ full, deploy, true); -+ } else { -+ rpath_lib = std::make_unique(patch_args.at("pe"), -+ full, deploy, true); -+ } -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -+ << " it contains references that are too long.\n"; -+ return ExitConditions::RENAME_FAILURE; - } - if (!rpath_lib->ExecuteRename()) { - std::cerr << "Library rename failed\n"; -@@ -110,9 +117,17 @@ int main(int argc, const char* argv[]) { - return ExitConditions::CLI_FAILURE; - } - if (report_args.find("pe") != report_args.end()) { -- LibRename portable_executable(report_args.at("pe"), std::string(), -- false, false, true); -- portable_executable.ExecuteRename(); -+ try { -+ LibRename portable_executable( -+ report_args.at("pe"), std::string(), false, false, true); -+ portable_executable.ExecuteRename(); -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr -+ << "Unable to parse command line for reporting\n" -+ << "run command with --help flag for accepted command " -+ "line arguments\n"; -+ return ExitConditions::CLI_FAILURE; -+ } - } else { - CoffReaderWriter coff_reader(report_args.at("coff")); - CoffParser coff(&coff_reader); -diff --git a/src/utils.cxx b/src/utils.cxx -index a22dcd5..5258caa 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -11,6 +11,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -20,6 +21,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -484,6 +486,12 @@ void replace_path_characters(char* path, size_t len) { - * \param bsize the lengh of the padding to add - */ - char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+ // If str_size > bsize we get inappropriate conversion -+ // from signed to unsigned -+ if (str_size > bsize) { -+ debug("Padding string is greater than max string size allowed"); -+ return nullptr; -+ } - size_t const extended_buf = bsize - str_size + 2; - char* padded_path = new char[bsize + 1]; - for (DWORD i = 0, j = 0; i < bsize && j < str_size; ++i) { -@@ -515,17 +523,16 @@ int get_padding_length(const std::string& name) { - return count; - } - --std::string strip_padding(const std::string &lib) --{ -+std::string strip_padding(const std::string& lib) { - // One of the padding characters is a legitimate - // path separator -- int pad_len = get_padding_length(lib)-1; -+ int const pad_len = get_padding_length(lib) - 1; - // Capture the drive and drive separator -- std::string::const_iterator p = lib.cbegin(); -- std::string::const_iterator e = lib.cbegin()+2; -- std::string stripped_drive(p, e); -+ std::string::const_iterator const p = lib.cbegin(); -+ std::string::const_iterator e = lib.cbegin() + 2; -+ std::string const stripped_drive(p, e); - e = e + pad_len; -- std::string path_remainder(e, lib.end()); -+ std::string const path_remainder(e, lib.end()); - return stripped_drive + path_remainder; - } - -@@ -545,6 +552,36 @@ std::string mangle_name(const std::string& name) { - // relative paths, assume they're relative to the CWD of the linker (as they have to be) - abs_out = join({GetCWD(), name}, "\\"); - } -+ // Now that we have the full path, check size -+ if (abs_out.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ // strip prefix -+ std::string pre = GetSpackEnv("SPACK_STAGE_DIR"); -+ if (pre.empty()) { -+ pre = GetCWD(); -+ } -+ std::string const rel = R"(\\?\)" + lstrip(abs_out, pre); -+ // Get SFN length so we can create buffer -+ DWORD const sfn_size = GetShortPathNameA(rel.c_str(), nullptr, 0); -+ char* sfn = new char[sfn_size + 1]; -+ GetShortPathNameA(rel.c_str(), sfn, rel.length()); -+ // sfn is null terminated per win32 api -+ std::string const rel_sfn = std::string(sfn); -+ std::string const new_abs_out = join({pre, rel_sfn}, "\\"); -+ // If new, shortened path is too long, bail -+ if (new_abs_out.length() > MAX_NAME_LEN) { -+ std::cerr << "DLL path " << abs_out << " too long to relocate.\n"; -+ std::cerr << "Shortened DLL path " << new_abs_out -+ << " also too long to relocate.\n"; -+ std::cerr << "Please move prefix " << pre -+ << " to a shorter directory.\n"; -+ delete[] sfn; -+ throw SpackCompilerWrapperError( -+ "DLL Path too long, cannot be relocated."); -+ } -+ delete[] sfn; -+ abs_out = new_abs_out; -+ } - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -579,7 +616,7 @@ bool SpackInstalledLib(const std::string& lib) { - "unset"); - return false; - } -- std::string stripped_lib = strip_padding(lib); -+ std::string const stripped_lib = strip_padding(lib); - startswith(stripped_lib, prefix); - } - -@@ -732,3 +769,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { - } - return nullptr; - } -+ -+SpackCompilerWrapperError::SpackCompilerWrapperError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* SpackCompilerWrapperError::what() const { -+ return exception::what(); -+} -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index 83e0358..b692e38 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -172,7 +172,7 @@ void replace_path_characters(char* path, size_t len); - - void replace_special_characters(char* mangled, size_t len); - --bool SpackInstalledLib(const std::string &lib); -+bool SpackInstalledLib(const std::string& lib); - - // File and File handle helpers // - -@@ -237,4 +237,10 @@ const std::map path_to_special_characters{{'\\', '|'}, - {'/', '|'}, - {':', ';'}}; - -+class SpackCompilerWrapperError : public std::runtime_error { -+ public: -+ SpackCompilerWrapperError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index 387b245..5671515 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,7 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include -+#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -102,7 +102,9 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { - char* new_lib_pth = - pad_path(new_library_loc.c_str(), - static_cast(new_library_loc.size())); -- -+ if (!new_lib_pth) { -+ return false; -+ } - replace_special_characters(new_lib_pth, MAX_NAME_LEN); - - // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -@@ -404,11 +406,18 @@ bool LibRename::ExecuteLibRename() { - std::cerr << err; - return false; - } -- std::string mangled_name = mangle_name(this->pe); -- if (!coff_parser.NormalizeName(mangled_name)) { -- std::cerr << "Unable to normalize name: " << mangled_name << "\n"; -+ try { -+ std::string mangled_name = mangle_name(this->pe); -+ if (!coff_parser.NormalizeName(mangled_name)) { -+ std::cerr << "Unable to normalize name: " << mangled_name << "\n"; -+ return false; -+ } -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Unable to mangle name, DLL name " << this->pe -+ << " too long\n"; - return false; - } -+ - return true; - } - diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch deleted file mode 100644 index 775b9f78310..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/long_path_support.patch +++ /dev/null @@ -1,475 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index cbc9f76..9979ea1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -26,6 +26,9 @@ jobs: - - name: Remove Git Bash tools - run: | - rmdir /s /q "C:\Program Files\Git\usr" -+ - name: enable 8.3 names -+ run: | -+ fsutil 8dot3name set 0 - - name: "Test RPath" - run: | - test\setup_and_drive_test.bat -diff --git a/Makefile b/Makefile -index 6a0f625..a86ace7 100644 ---- a/Makefile -+++ b/Makefile -@@ -141,10 +141,41 @@ test_zerowrite: build_zerowrite_test - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_long_paths: build_and_check_test_sample -+ mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ xcopy test\main.cxx tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ rename calc.cxx verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.cxx -+ copy ..\..\..\..\cl.exe cl.exe -+ -@ if NOT EXIST "link.exe" mklink link.exe cl.exe -+ cl /c /EHsc "verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I include -+ cl /c /EHsc main.cxx /I include -+ link $(LFLAGS) verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.obj /DLL -+ link $(LFLAGS) main.obj verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -+ tester.exe -+ cd ../../../.. -+ -+test_relocate_long_paths: test_long_paths -+ cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -+ -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe -+ cd .. -+ mkdir tmp_bin -+ mkdir tmp_lib -+ move evenlongersubdirectoryname\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll -+ move evenlongersubdirectoryname\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ cd evenlongersubdirectoryname -+ del tester.exe -+ link main.obj ..\tmp_lib\verylongfilepathnamethatwillmostlikelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -+ .\tester.exe -+ cd ../../../.. -+ - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_long_paths test_pipe_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index f6b273c..3cda566 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -378,7 +378,8 @@ bool CoffParser::NormalizeName(std::string& name) { - // The dll is found with and without an extenion, depending on the context of the location - // i.e. in the section data, it can be found with both an extension and extensionless - // whereas in the symbol table or linker member strings, it's always found without an extension -- std::string const name_no_ext = strip(name, ".dll"); -+ std::string const name_no_dll = strip(name, ".dll"); -+ std::string const name_no_ext = strip(name_no_dll, ".DLL"); - // Flag allowing us to skip multiple attempts - // to rename the long names member this name - bool long_name_renamed = false; -diff --git a/src/ld.cxx b/src/ld.cxx -index ff9bdde..890f364 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,7 @@ - #include "ld.h" - #include - #include -+#include - #include "coff_parser.h" - #include "coff_reader_writer.h" - #include "linker_invocation.h" -@@ -34,7 +35,14 @@ DWORD LdInvocation::InvokeToolchain() { - // We're creating a dll, we need to create an appropriate import lib - if (!link_run.IsExeLink()) { - std::string const imp_lib_name = link_run.get_implib_name(); -- std::string dll_name = link_run.get_mangled_out(); -+ std::string dll_name; -+ try { -+ dll_name = link_run.get_mangled_out(); -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Unable to mangle PE " << link_run.get_out() -+ << " name is too long\n"; -+ return ExitConditions::NORMALIZE_NAME_FAILURE; -+ } - std::string const abs_out_imp_lib_name = imp_lib_name + ".dll-abs.lib"; - std::string def = "-def "; - std::string piped_args = link_run.get_lib_link_args(); -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 5e7b60b..fc6316f 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -50,7 +50,10 @@ void LinkerInvocation::Parse() { - this->is_exe_ = false; - } else if (startswith(normal_token, "-out") || - startswith(normal_token, "/out")) { -- this->output_ = split(*token, ":")[1]; -+ StrList split_out = split(*token, ":", 1); -+ StrList const split_name = -+ StrList(split_out.begin() + 1, split_out.end()); -+ this->output_ = join(split_name, "\\"); - } else if (endswith(normal_token, ".obj")) { - this->objs_.push_back(*token); - } else if (startswith(normal_token, "@") && -@@ -67,7 +70,11 @@ void LinkerInvocation::Parse() { - } - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; - if (this->output_.empty()) { -- this->output_ = strip(this->objs_.front(), ".obj") + ext; -+ // with no "out" argument, the linker -+ // will place the file in the CWD -+ std::string const name_obj = this->objs_.front(); -+ std::string const filename = split(name_obj, "\\").back(); -+ this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; - } - this->name_ = strip(this->output_, ext); - if (this->implibname_.empty()) { -diff --git a/src/main.cxx b/src/main.cxx -index 9458d17..26d940d 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -89,12 +89,19 @@ int main(int argc, const char* argv[]) { - } - DEBUG = debug; - std::unique_ptr rpath_lib; -- if (has_coff) { -- rpath_lib = std::make_unique( -- patch_args.at("pe"), patch_args.at("coff"), full, deploy, true); -- } else { -- rpath_lib = std::make_unique(patch_args.at("pe"), full, -- deploy, true); -+ try { -+ if (has_coff) { -+ rpath_lib = std::make_unique(patch_args.at("pe"), -+ patch_args.at("coff"), -+ full, deploy, true); -+ } else { -+ rpath_lib = std::make_unique(patch_args.at("pe"), -+ full, deploy, true); -+ } -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -+ << " it contains references that are too long.\n"; -+ return ExitConditions::RENAME_FAILURE; - } - if (!rpath_lib->ExecuteRename()) { - std::cerr << "Library rename failed\n"; -@@ -110,9 +117,17 @@ int main(int argc, const char* argv[]) { - return ExitConditions::CLI_FAILURE; - } - if (report_args.find("pe") != report_args.end()) { -- LibRename portable_executable(report_args.at("pe"), std::string(), -- false, false, true); -- portable_executable.ExecuteRename(); -+ try { -+ LibRename portable_executable( -+ report_args.at("pe"), std::string(), false, false, true); -+ portable_executable.ExecuteRename(); -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr -+ << "Unable to parse command line for reporting\n" -+ << "run command with --help flag for accepted command " -+ "line arguments\n"; -+ return ExitConditions::CLI_FAILURE; -+ } - } else { - CoffReaderWriter coff_reader(report_args.at("coff")); - CoffParser coff(&coff_reader); -diff --git a/src/utils.cxx b/src/utils.cxx -index a22dcd5..eb1ce9c 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -11,15 +11,18 @@ - #include - #include - #include -+#include - #include - #include - #include -+#include - - #include - #include - #include - #include - #include -+#include - #include - #include - #include -@@ -122,16 +125,25 @@ std::wstring ConvertASCIIToWide(const std::string& str) { - * Decomposes the input string into a list separated by - * delim - * -+ * Count determines how many delims will be processed -+ * if count > 0 -+ * - * Returns the list produced by breaking up input string s on delim - */ --StrList split(const std::string& str, const std::string& delim) { -+StrList split(const std::string& str, const std::string& delim, -+ const u_int count) { - size_t pos_start = 0; - size_t pos_end; - size_t const delim_len = delim.length(); - std::string token; - StrList res = StrList(); -- -- while ((pos_end = str.find(delim, pos_start)) != std::string::npos) { -+ u_int delim_count = count; -+ if (!count) { -+ delim_count += 1; -+ } -+ u_int delim_found = 0; -+ while (((pos_end = str.find(delim, pos_start)) != std::string::npos) && -+ delim_found < delim_count) { - size_t const token_len = pos_end - pos_start; - token = str.substr(pos_start, token_len); - pos_start = pos_end + delim_len; -@@ -139,6 +151,9 @@ StrList split(const std::string& str, const std::string& delim) { - continue; - } - res.push_back(token); -+ if (count) { -+ delim_found += 1; -+ } - } - res.push_back(str.substr(pos_start)); - return res; -@@ -163,7 +178,7 @@ std::string strip(const std::string& str, const std::string& substr) { - std::string lstrip(const std::string& str, const std::string& substr) { - if (!startswith(str, substr)) - return str; -- return str.substr(substr.size() - 1, str.size()); -+ return str.substr(substr.size(), str.size()); - } - - /** -@@ -484,6 +499,12 @@ void replace_path_characters(char* path, size_t len) { - * \param bsize the lengh of the padding to add - */ - char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+ // If str_size > bsize we get inappropriate conversion -+ // from signed to unsigned -+ if (str_size > bsize) { -+ debug("Padding string is greater than max string size allowed"); -+ return nullptr; -+ } - size_t const extended_buf = bsize - str_size + 2; - char* padded_path = new char[bsize + 1]; - for (DWORD i = 0, j = 0; i < bsize && j < str_size; ++i) { -@@ -515,20 +536,65 @@ int get_padding_length(const std::string& name) { - return count; - } - --std::string strip_padding(const std::string &lib) --{ -+std::string strip_padding(const std::string& lib) { - // One of the padding characters is a legitimate - // path separator -- int pad_len = get_padding_length(lib)-1; -+ int const pad_len = get_padding_length(lib) - 1; - // Capture the drive and drive separator -- std::string::const_iterator p = lib.cbegin(); -- std::string::const_iterator e = lib.cbegin()+2; -- std::string stripped_drive(p, e); -+ std::string::const_iterator const p = lib.cbegin(); -+ std::string::const_iterator e = lib.cbegin() + 2; -+ std::string const stripped_drive(p, e); - e = e + pad_len; -- std::string path_remainder(e, lib.end()); -+ std::string const path_remainder(e, lib.end()); - return stripped_drive + path_remainder; - } - -+std::string getSFN(const std::string& path) { -+ std::string const escaped = R"(\\?\)" + path; -+ // Get SFN length so we can create buffer -+ DWORD const sfn_size = -+ GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT -+ char* sfn = new char[sfn_size + 1]; -+ GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ // sfn is null terminated per win32 api -+ std::string s_sfn = std::string(sfn); -+ delete[] sfn; -+ return s_sfn; -+} -+ -+/** -+ * Replace path after a given prefix with the SFN representation -+ */ -+std::string short_name_post_prefix(const std::string& path) { -+ // strip prefix -+ std::string pre = GetSpackEnv("SPACK_CONTEXT_ROOT"); -+ if (pre.empty()) { -+ pre = GetCWD(); -+ } -+ // Get SFN for path to name -+ // Use "disable string parsing" prefix in case -+ // the path is too long -+ std::string const abs_sfn = getSFN(path); -+ // Get SFN for path to name prefix -+ std::string const pre_sfn = getSFN(pre); -+ // Strip prefix SFN so we can prepend the real prefix to it -+ // Prefix should be spack stage root, which allows us the maximal -+ // possible path shortening without effecting potential naming collisions -+ // and still allowing us to determine we're in a spack context -+ std::string const rel_sfn = lstrip(abs_sfn, pre_sfn); -+ std::string new_abs_out = pre + rel_sfn; -+ if (new_abs_out.length() > MAX_NAME_LEN) { -+ std::cerr << "DLL path " << path << " too long to relocate.\n"; -+ std::cerr << "Shortened DLL path " << new_abs_out -+ << " also too long to relocate.\n"; -+ std::cerr << "Please move Spack prefix " -+ << " to a shorter directory.\n"; -+ throw SpackCompilerWrapperError( -+ "DLL Path too long, cannot be relocated."); -+ } -+ return new_abs_out; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -545,6 +611,13 @@ std::string mangle_name(const std::string& name) { - // relative paths, assume they're relative to the CWD of the linker (as they have to be) - abs_out = join({GetCWD(), name}, "\\"); - } -+ // Now that we have the full path, check size -+ if (abs_out.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const new_abs_out = short_name_post_prefix(abs_out); -+ // If new, shortened path is too long, bail -+ abs_out = new_abs_out; -+ } - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -579,7 +652,7 @@ bool SpackInstalledLib(const std::string& lib) { - "unset"); - return false; - } -- std::string stripped_lib = strip_padding(lib); -+ std::string const stripped_lib = strip_padding(lib); - startswith(stripped_lib, prefix); - } - -@@ -732,3 +805,10 @@ char* findstr(char* search_str, const char* substr, size_t size) { - } - return nullptr; - } -+ -+SpackCompilerWrapperError::SpackCompilerWrapperError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* SpackCompilerWrapperError::what() const { -+ return exception::what(); -+} -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index 83e0358..eaa691b 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -71,7 +71,8 @@ std::wstring ConvertASCIIToWide(const std::string& str); - // Splits argument "s" by delineator delim - // Returns vector of strings, if delim is present - // Returns a single item list --StrList split(const std::string& s, const std::string& delim); -+StrList split(const std::string& s, const std::string& delim, -+ const u_int count = 0); - - //Strips substr off the RHS of the larger string - std::string strip(const std::string& s, const std::string& substr); -@@ -162,6 +163,8 @@ bool IsPathAbsolute(const std::string& pth); - - bool hasPathCharacters(const std::string& name); - -+std::string short_name_post_prefix(const std::string& path); -+ - std::string mangle_name(const std::string& name); - - int get_padding_length(const std::string& name); -@@ -172,7 +175,7 @@ void replace_path_characters(char* path, size_t len); - - void replace_special_characters(char* mangled, size_t len); - --bool SpackInstalledLib(const std::string &lib); -+bool SpackInstalledLib(const std::string& lib); - - // File and File handle helpers // - -@@ -237,4 +240,10 @@ const std::map path_to_special_characters{{'\\', '|'}, - {'/', '|'}, - {':', ';'}}; - -+class SpackCompilerWrapperError : public std::runtime_error { -+ public: -+ SpackCompilerWrapperError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index 387b245..fd3c163 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -92,17 +92,28 @@ bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { - return false; - } - LibraryFinder lib_finder; -- std::string const new_library_loc = -+ std::string new_library_loc = - lib_finder.FindLibrary(file_name, dll_path); - if (new_library_loc.empty()) { - std::cerr << "Unable to find library " << file_name << " from " - << dll_path << " for relocation" << "\n"; - return false; - } -+ if (new_library_loc.length() > MAX_NAME_LEN) { -+ try { -+ std::string const short_lib_loc = -+ short_name_post_prefix(new_library_loc); -+ new_library_loc = short_lib_loc; -+ } catch (SpackCompilerWrapperError& e) { -+ return false; -+ } -+ } - char* new_lib_pth = - pad_path(new_library_loc.c_str(), - static_cast(new_library_loc.size())); -- -+ if (!new_lib_pth) { -+ return false; -+ } - replace_special_characters(new_lib_pth, MAX_NAME_LEN); - - // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -@@ -404,11 +415,18 @@ bool LibRename::ExecuteLibRename() { - std::cerr << err; - return false; - } -- std::string mangled_name = mangle_name(this->pe); -- if (!coff_parser.NormalizeName(mangled_name)) { -- std::cerr << "Unable to normalize name: " << mangled_name << "\n"; -+ try { -+ std::string mangled_name = mangle_name(this->pe); -+ if (!coff_parser.NormalizeName(mangled_name)) { -+ std::cerr << "Unable to normalize name: " << mangled_name << "\n"; -+ return false; -+ } -+ } catch (const SpackCompilerWrapperError& e) { -+ std::cerr << "Unable to mangle name, DLL name " << this->pe -+ << " too long\n"; - return false; - } -+ - return true; - } - diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 78eaabc44d3..990b5e02a6e 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): version("develop", branch="main") with when("@develop platform=windows"): - patch("def-and-exe-enhanced-support.patch") + patch("rc_dll_id.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get @@ -69,17 +69,7 @@ def bin_dir(self) -> pathlib.Path: def setup_dependent_package(self, module, dependent_spec): def _spack_compiler_attribute(*, language: str) -> str: compiler_pkg = dependent_spec[language].package - if sys.platform != "win32": - # On non-Windows we return the appropriate path to the compiler wrapper - return str(self.bin_dir() / compiler_pkg.compiler_wrapper_link_paths[language]) - - # On Windows we return the real compiler - if language == "c": - return compiler_pkg.cc - elif language == "cxx": - return compiler_pkg.cxx - elif language == "fortran": - return compiler_pkg.fortran + return str(self.bin_dir() / compiler_pkg.compiler_wrapper_link_paths[language]) if dependent_spec.has_virtual_dependency("c"): setattr(module, "spack_cc", _spack_compiler_attribute(language="c")) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch deleted file mode 100644 index bdf8f0ecfb1..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/path_fixup.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index e642c29..b026757 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -451,7 +451,7 @@ void replace_path_characters(char in[], int len) - * null terminators. - * \param bsize the lengh of the padding to add - */ --char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) -+char * pad_path(const char *pth, DWORD str_size, DWORD bsize) - { - size_t extended_buf = bsize - str_size + 2; - char * padded_path = new char[bsize+1]; -@@ -524,7 +524,9 @@ std::string mangle_name(const std::string &name) - * \param name string to check for path characters - */ - bool hasPathCharacters(const std::string &name) { -- for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ -+ typedef std::map::const_iterator PathCharMap; -+ for(PathCharMap it = path_to_special_characters.begin(); -+ it != path_to_special_characters.end(); ++it){ - if(!(name.find(it->first) == std::string::npos)){ - return true; - } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch deleted file mode 100644 index 8f680fb7173..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/paths.patch +++ /dev/null @@ -1,325 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index 220be6f..e642c29 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -408,6 +408,131 @@ std::string reportLastError() - return std::system_category().message(error); - } - -+/** -+ * Replaces characters used to mangle path characters with -+ * valid path characters -+ * -+ * \param in a pointer to the string to replace the mangled path characters in -+ * \param len the length of the mangled path -+ */ -+void replace_special_characters(char in[], int len) -+{ -+ for (int i = 0; i < len; ++i) { -+ if (special_character_to_path.count(in[i])) -+ { -+ in[i] = special_character_to_path.at(in[i]); -+ } -+ -+ } -+} -+ -+/** -+ * Replaces path characters with special, non path, replacement characters -+ * -+ * \param in a pointer to the string to have its path characters replace with special placeholders -+ * \param len the length of the path to be mangled -+ */ -+void replace_path_characters(char in[], int len) -+{ -+ for (int i = 0; i < len; i++ ) { -+ if (path_to_special_characters.count(in[i])) -+ in[i] = path_to_special_characters.at(in[i]); -+ } -+} -+ -+/** -+ * Pads a given path with an amount of padding of special characters -+ * Paths are padded after the drive separator but before any path -+ * characters, i.e. C:[\\\\\\\]\path\to\exe with the section in [] -+ * being the padded component -+ * -+ * \param pth a pointer to the path to be padded -+ * \param str_size the length of the path - not including any -+ * null terminators. -+ * \param bsize the lengh of the padding to add -+ */ -+char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) -+{ -+ size_t extended_buf = bsize - str_size + 2; -+ char * padded_path = new char[bsize+1]; -+ for(int i = 0, j = 0; i < bsize && j < str_size; ++i){ -+ if(i < 2){ -+ padded_path[i] = pth[j]; -+ ++j; -+ } -+ else if(i < extended_buf){ -+ padded_path[i] = '|'; -+ } -+ else{ -+ padded_path[i] = pth[j]; -+ ++j; -+ } -+ } -+ padded_path[bsize] = '\0'; -+ return padded_path; -+} -+ -+/** -+ * Given a padded library path, return how much the path -+ * has been padded -+ * -+ * \param name the path for which to determine pad count -+ */ -+int get_padding_length(const std::string &name) -+{ -+ int c = 0; -+ std::string::const_iterator p = name.cbegin(); -+ p+=2; -+ while(p != name.end() && *p == '\\') { -+ ++c; -+ ++p; -+ } -+ return c; -+} -+ -+/** -+ * Mangles a string representing a path to have no path characters -+ * instead path characters (i.e. \\, :, etc) are replaced with -+ * special replacement characters -+ * -+ * \param name the string to be mangled -+ */ -+std::string mangle_name(const std::string &name) -+{ -+ std::string abs_out; -+ std::string mangled_abs_out; -+ if(IsPathAbsolute(name)){ -+ abs_out = name; -+ } -+ else{ -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ abs_out = join({GetCWD(), name}, "\\"); -+ } -+ char * chr_abs_out = new char [abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ replace_path_characters(chr_abs_out, abs_out.length()); -+ char * padded_path = pad_path(chr_abs_out, abs_out.length()); -+ mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); -+ -+ delete chr_abs_out; -+ delete padded_path; -+ return mangled_abs_out; -+} -+ -+/** -+ * Determines whether a string contains path characters -+ * \param name string to check for path characters -+ */ -+bool hasPathCharacters(const std::string &name) { -+ for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ -+ if(!(name.find(it->first) == std::string::npos)){ -+ return true; -+ } -+ } -+ return false; -+} -+ -+ - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} - - std::string LibraryFinder::FindLibrary(const std::string &lib_name, const std::string &lib_path) { -diff --git a/src/utils.h b/src/utils.h -index 7fbde84..86a9433 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -135,6 +135,18 @@ std::string GetCWD(); - // Returns boolean indication whether pth is absolute - bool IsPathAbsolute(const std::string &pth); - -+bool hasPathCharacters(const std::string &name); -+ -+std::string mangle_name(const std::string &name); -+ -+int get_padding_length(const std::string &name); -+ -+char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+ -+void replace_path_characters(char in[], int len); -+ -+void replace_special_characters(char in[], int len); -+ - // File and File handle helpers // - - // Returns File offset given RVA -@@ -184,4 +196,16 @@ public: - void EvalSearchPaths(); - }; - -+const std::map special_character_to_path{ -+ {'|', '\\'}, -+ {';', ':'} -+}; -+ -+const std::map path_to_special_characters{ -+ {'\\', '|'}, -+ {'/', '|'}, -+ {':', ';'} -+}; -+ -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index 1a59fd4..e19e53f 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -14,127 +14,6 @@ - using CoffMembers = std::vector; - - --const std::map special_character_to_path{ -- {'|', '\\'}, -- {';', ':'} --}; -- --const std::map path_to_special_characters{ -- {'\\', '|'}, -- {'/', '|'}, -- {':', ';'} --}; -- --/** -- * Replaces characters used to mangle path characters with -- * valid path characters -- * -- * \param in a pointer to the string to replace the mangled path characters in -- * \param len the length of the mangled path -- */ --void replace_special_characters(char in[], int len) --{ -- for (int i = 0; i < len; ++i) { -- if (special_character_to_path.count(in[i])) -- { -- in[i] = special_character_to_path.at(in[i]); -- } -- -- } --} -- --/** -- * Replaces path characters with special, non path, replacement characters -- * -- * \param in a pointer to the string to have its path characters replace with special placeholders -- * \param len the length of the path to be mangled -- */ --void replace_path_characters(char in[], int len) --{ -- for (int i = 0; i < len; i++ ) { -- if (path_to_special_characters.count(in[i])) -- in[i] = path_to_special_characters.at(in[i]); -- } --} -- --/** -- * Pads a given path with an amount of padding of special characters -- * Paths are padded after the drive separator but before any path -- * characters, i.e. C:[\\\\\\\]\path\to\exe with the section in [] -- * being the padded component -- * -- * \param pth a pointer to the path to be padded -- * \param str_size the length of the path - not including any -- * null terminators. -- * \param bsize the lengh of the padding to add -- */ --char * pad_path(const char *pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN) --{ -- size_t extended_buf = bsize - str_size + 2; -- char * padded_path = new char[bsize+1]; -- for(int i = 0, j = 0; i < bsize && j < str_size; ++i){ -- if(i < 2){ -- padded_path[i] = pth[j]; -- ++j; -- } -- else if(i < extended_buf){ -- padded_path[i] = '|'; -- } -- else{ -- padded_path[i] = pth[j]; -- ++j; -- } -- } -- padded_path[bsize] = '\0'; -- return padded_path; --} -- --/** -- * Given a padded library path, return how much the path -- * has been padded -- * -- * \param name the path for which to determine pad count -- */ --int get_padding_length(const std::string &name) --{ -- int c = 0; -- std::string::const_iterator p = name.cbegin(); -- p+=2; -- while(p != name.end() && *p == '\\') { -- ++c; -- ++p; -- } -- return c; --} -- --/** -- * Mangles a string representing a path to have no path characters -- * instead path characters (i.e. \\, :, etc) are replaced with -- * special replacement characters -- * -- * \param name the string to be mangled -- */ --std::string mangle_name(const std::string &name) --{ -- std::string abs_out; -- std::string mangled_abs_out; -- if(IsPathAbsolute(name)){ -- abs_out = name; -- } -- else{ -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -- char * chr_abs_out = new char [abs_out.length() + 1]; -- strcpy(chr_abs_out, abs_out.c_str()); -- replace_path_characters(chr_abs_out, abs_out.length()); -- char * padded_path = pad_path(chr_abs_out, abs_out.length()); -- mangled_abs_out = std::string(padded_path, MAX_NAME_LEN); -- -- delete chr_abs_out; -- delete padded_path; -- return mangled_abs_out; --} - - /** - * Parses the command line of a given linker invocation and stores information -@@ -888,16 +767,6 @@ bool reportCoff(CoffParser &coff) - return true; - } - -- --bool hasPathCharacters(const std::string &name) { -- for(std::map::const_iterator it = path_to_special_characters.begin(); it != path_to_special_characters.end(); ++it){ -- if(!(name.find(it->first) == std::string::npos)){ -- return true; -- } -- } -- return false; --} -- - /* - * Checks a DLL name for special characters, if we're deploying, a path character, if we're - * relocating a spack sigil diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch deleted file mode 100644 index fcf4c975ba0..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/pipe.patch +++ /dev/null @@ -1,221 +0,0 @@ -diff --git a/Makefile b/Makefile -index bb284d2..0a6186f 100644 ---- a/Makefile -+++ b/Makefile -@@ -108,11 +108,28 @@ test_relocate_dll: build_and_check_test_sample - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe -+ cd ../.. -+ -+test_pipe_overflow: build_and_check_test_sample -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ -+build_zerowrite_test: test\writezero.obj -+ link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ -+ -+test_zerowrite: build_zerowrite_test -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\writezero.exe -+ cl /c EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% - - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll -+test: test_wrapper test_relocate_exe test_relocate_dll test_pipe_overflow - - - clean : clean-test clean-cl -@@ -128,4 +145,5 @@ clean-cl : - del cl.exe - - clean-test: -- rmdir /q /s tmp -+ -@ if EXIST "tmp" rmdir /q /s "tmp" -+ -diff --git a/src/execute.cxx b/src/execute.cxx -index 77d49a6..0819d81 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -161,7 +161,7 @@ int ExecuteCommand::PipeChildToStdErr() - CHAR chBuf[BUFSIZE]; - BOOL bSuccess = TRUE; - HANDLE hParentOut; -- if (this->write_to_file) { -+ if (this->write_to_file && this->fileout != INVALID_HANDLE_VALUE) { - hParentOut = this->fileout; - } - else { -@@ -171,10 +171,35 @@ int ExecuteCommand::PipeChildToStdErr() - for (;;) - { - bSuccess = ReadFile( this->ChildStdErr_Rd, chBuf, BUFSIZE, &dwRead, NULL); -- if( ! bSuccess || dwRead == 0 ) break; -- -- bSuccess = WriteFile(hParentOut, chBuf, -- dwRead, &dwWritten, NULL); -+ // For an explanation behind the use of termianted here -+ // see the docstring on pipechildtostdout -+ if( ! bSuccess || (dwRead == 0 && this->terminated) ) break; -+ if(dwRead != 0) { -+ bSuccess = WriteFile(hParentOut, chBuf, -+ dwRead, &dwWritten, NULL); -+ if (dwWritten < dwRead && bSuccess){ -+ // incomplete write but not a failure -+ // since bSuccess is true -+ // So lets write until bSuccess is false or -+ // until all bytes are written -+ int currentPos = dwWritten; -+ while((dwWritten < dwRead) || dwWritten == 0) { -+ dwRead = dwRead - dwWritten; -+ CHAR * partialBuf = new CHAR[dwRead]; -+ for(int i = 0; i < dwRead; ++i) { -+ partialBuf[i] = chBuf[currentPos + i]; -+ } -+ bSuccess = WriteFile(hParentOut, partialBuf, -+ dwRead, &dwWritten, NULL); -+ delete partialBuf; -+ if (! bSuccess) break; -+ currentPos += dwWritten; -+ } -+ } -+ if (! bSuccess ) { -+ break; -+ } -+ } - if (! bSuccess ) break; - } - return !bSuccess; -@@ -192,7 +217,7 @@ int ExecuteCommand::PipeChildToStdout() - CHAR chBuf[BUFSIZE]; - BOOL bSuccess = TRUE; - HANDLE hParentOut; -- if (this->write_to_file) { -+ if (this->write_to_file && this->fileout != INVALID_HANDLE_VALUE) { - hParentOut = this->fileout; - } - else { -@@ -202,10 +227,41 @@ int ExecuteCommand::PipeChildToStdout() - for (;;) - { - bSuccess = ReadFile( this->ChildStdOut_Rd, chBuf, BUFSIZE, &dwRead, NULL); -- if( ! bSuccess || dwRead == 0 ) break; -- -- bSuccess = WriteFile(hParentOut, chBuf, -- dwRead, &dwWritten, NULL); -+ // Typically dwRead == 0 indicates the writer end of the pipe has ceased writing -+ // however if the writer were to invoke WriteFile with a size of 0, dwRead would -+ // be 0 but the writer would not have terminated. -+ // As such we need an explicit indication the writer process has termianted. -+ // From the MSVC docs: -+ // If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, -+ // the other end of the pipe called the WriteFile function with nNumberOfBytesToWrite -+ // set to zero. -+ if( ! bSuccess || (dwRead == 0 && this->terminated) ) break; -+ if(dwRead != 0){ -+ bSuccess = WriteFile(hParentOut, chBuf, -+ dwRead, &dwWritten, NULL); -+ if (dwWritten < dwRead && bSuccess){ -+ // incomplete write but not a failure -+ // since bSuccess is true -+ // So lets write until bSuccess is false or -+ // until all bytes are written -+ int currentPos = dwWritten; -+ while((dwWritten < dwRead) || dwWritten == 0) { -+ dwRead = dwRead - dwWritten; -+ CHAR * partialBuf = new CHAR[dwRead]; -+ for(int i = 0; i < dwRead; ++i) { -+ partialBuf[i] = chBuf[currentPos + i]; -+ } -+ bSuccess = WriteFile(hParentOut, partialBuf, -+ dwRead, &dwWritten, NULL); -+ delete partialBuf; -+ if (! bSuccess) break; -+ currentPos += dwWritten; -+ } -+ } -+ if (! bSuccess ) { -+ break; -+ } -+ } - if (! bSuccess ) break; - } - return !bSuccess; -@@ -242,6 +298,7 @@ int ExecuteCommand::ReportExitCode() - if(exit_code != STILL_ACTIVE) - break; - } -+ this->terminated = true; - CloseHandle(this->procInfo.hProcess); - return exit_code; - } -@@ -294,9 +351,17 @@ bool ExecuteCommand::Execute(const std::string &filename) - */ - int ExecuteCommand::Join() - { -+ // Allow primary command to conclude -+ // ensures stdout and stderr readers -+ // exit only once primary command process -+ // has concluded -+ // The child and std err pipe readers -+ // will not terminate under normal conditions -+ // unless this process concludes and sets the terminate flag -+ int commandError = this->exit_code_future.get(); - if(!this->child_out_future.get()) - return -999; - if(!this->child_err_future.get()) - return -999; -- return this->exit_code_future.get(); -+ return commandError; - } -diff --git a/src/execute.h b/src/execute.h -index 94c5a74..edb360b 100644 ---- a/src/execute.h -+++ b/src/execute.h -@@ -61,8 +61,9 @@ private: - SECURITY_ATTRIBUTES saAttr; - SECURITY_ATTRIBUTES saAttrErr; - HANDLE fileout = INVALID_HANDLE_VALUE; -- bool write_to_file; -+ bool write_to_file = false; - bool cpw_initalization_failure = false; -+ bool terminated = false; - std::string base_command; - StrList command_args; - }; -diff --git a/test/lots-of-output.bat b/test/lots-of-output.bat -new file mode 100644 -index 0000000..878528d ---- /dev/null -+++ b/test/lots-of-output.bat -@@ -0,0 +1,3 @@ -+@echo OFF -+ -+for /l %%x in (1, 1, 1250) do echo Test boilerplate output to fill stdout line number %%x -diff --git a/test/writezero/writezero.cxx b/test/writezero/writezero.cxx -new file mode 100644 -index 0000000..b9ed88a ---- /dev/null -+++ b/test/writezero/writezero.cxx -@@ -0,0 +1,14 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define BUFSIZE 100 -+ -+int main(int argc, char ** argv) { -+ HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE); -+ char buff[BUFSIZE]; -+ WriteFile(stdOut, buff, 0, NULL, NULL); -+} -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch deleted file mode 100644 index eb75b1a7c51..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/quote_args.patch +++ /dev/null @@ -1,60 +0,0 @@ -diff --git a/Makefile b/Makefile -index c90ccce..31fcd5f 100644 ---- a/Makefile -+++ b/Makefile -@@ -22,7 +22,7 @@ PREFIX="$(MAKEDIR)\install\" - !ENDIF - - !IF "$(BUILD_TYPE)" == "DEBUG" --BUILD_CFLAGS = /Zi /fsanitize=address -+BUILD_CFLAGS = /Zi - BUILD_LINK = /DEBUG - !ENDIF - -@@ -59,7 +59,7 @@ setup_test: cl.exe - # smoke test - can the wrapper compile anything - build_and_check_test_sample : setup_test - cd tmp\test -- cl /c /EHsc ..\..\test\calc.cxx /DCALC_EXPORTS /I ..\..\test\include -+ cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER=\"calc.h\" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include - link $(LFLAGS) calc.obj /out:calc.dll /DLL - link $(LFLAGS) main.obj calc.lib /out:tester.exe -diff --git a/src/utils.cxx b/src/utils.cxx -index 8e153b0..220be6f 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -188,6 +188,13 @@ std::string quoteAsNeeded(std::string &str) { - // There are spaces or special characters in string, quote it - return "\"" + str + "\""; - } -+ if (str.find_first_of("\"") != std::string::npos) { -+ // If there are escaped quotes in input -+ // We need to escape them as well as we're adding another -+ // layer of indirection between builder and compiler -+ std::regex pattern("\""); -+ return std::regex_replace(str, pattern, "\\\""); -+ } - return str; - } - -diff --git a/test/calc.cxx b/test/src file/calc.cxx -similarity index 73% -rename from test/calc.cxx -rename to test/src file/calc.cxx -index cd7a136..b07cca2 100644 ---- a/test/calc.cxx -+++ b/test/src file/calc.cxx -@@ -3,7 +3,12 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+ -+#if defined(CALC_HEADER) -+#include CALC_HEADER /* "calc.h" */ -+#else - #include "calc.h" -+#endif - - extern "C" int add(int a, int b) - { diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch deleted file mode 100644 index ca135eff345..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/quoting.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index aa65bcb..a22dcd5 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -211,16 +211,23 @@ void lower(std::string& str) { - } - - std::string quoteAsNeeded(std::string& str) { -- if (str.find_first_of(" &<>|()") != std::string::npos) { -- // There are spaces or special characters in string, quote it -- return "\"" + str + "\""; -- } -+ // Note: the ordering if these two conditionals is important -+ // If the second conditional is executed first, the first -+ // will always be true as the second injects the string -+ // on which the first is conditioned -+ // Basically: If we find escaped strings: escape em again -+ // If we find space/special chars: escape the whole string -+ - if (str.find_first_of('\"') != std::string::npos) { - // If there are escaped quotes in input - // We need to escape them as well as we're adding another -- // layer of indirection between builder and compiler -+ // layer of indirection between caller and compiler - std::regex const pattern("\""); -- return std::regex_replace(str, pattern, "\\\""); -+ str = std::regex_replace(str, pattern, "\\\""); -+ } -+ if (str.find_first_of(" &<>|()") != std::string::npos) { -+ // There are spaces or special characters in string, quote it -+ str = "\"" + str + "\""; - } - return str; - } - diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch new file mode 100644 index 00000000000..a64d850c878 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch @@ -0,0 +1,474 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..1d5b7bd 100644 +--- a/Makefile ++++ b/Makefile +@@ -66,9 +66,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +81,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +95,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,9 +112,10 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +@@ -124,9 +127,10 @@ test_relocate_exe: build_and_check_test_sample + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -142,9 +146,10 @@ test_relocate_dll: build_and_check_test_sample + cd ../.. + + test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Pipe overflow test ++ @echo -------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" +@@ -154,18 +159,20 @@ build_zerowrite_test: test\writezero.obj + link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +189,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -200,9 +208,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +232,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..aded586 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,8 @@ + #include "ld.h" + #include + #include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +21,31 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_args, ++ this->lib_dir_args, this->obj_args})); ++ link_run.Parse(); ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ std::string const rc_file = createRC(link_run.get_out()); ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end ++ this->lib_args.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -99,3 +113,45 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(const std::string& pe_stage_name) { ++ const std::string template_base = ++ "STRINGTABLE\n" ++ "BEGIN\n"; ++ // This string table ID is completely arbitrary, HOWEVER ++ // Spack Core relies on this specific value ++ // If it is changed here, it must be changed in Spack Core ++ const std::string string_table_id = " 59673 "; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectations ++ std::string res_file_name = pe_name + ".res"; ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ return std::string(); ++ } ++ std::string abs_out = ++ EnsureValidLengthPath(MakePathAbsolute(pe_stage_name)); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char const* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ rc_out << template_base << string_table_id << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ return std::string(); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..764baf4 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -20,4 +20,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(const std::string& pe_stage_name); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..ef52041 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -84,6 +84,9 @@ void LinkerInvocation::Parse() { + // with no "out" argument, the linker + // will place the file in the CWD + std::string const name_obj = this->objs_.front(); ++ // std::string const filename = ++ // strip(strip(split(name_obj, "\\").back(), ".lib"), ".obj"); ++ // this->output_ = join({GetCWD(), filename}, "\\") + ext; + std::string const filename = split(name_obj, "\\").back(); + this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; + } +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..922785e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->obj_args, this->lib_args})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..2f8dd68 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -529,7 +529,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,7 +544,7 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; +@@ -623,6 +624,25 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +653,9 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +diff --git a/src/utils.h b/src/utils.h +index b04d929..3462020 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -188,7 +188,8 @@ std::string mangle_name(const std::string& name); + + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +197,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch new file mode 100644 index 00000000000..39aa5531237 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch @@ -0,0 +1,383 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..1d5b7bd 100644 +--- a/Makefile ++++ b/Makefile +@@ -66,9 +66,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +81,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +95,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,9 +112,10 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +@@ -124,9 +127,10 @@ test_relocate_exe: build_and_check_test_sample + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -142,9 +146,10 @@ test_relocate_dll: build_and_check_test_sample + cd ../.. + + test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Pipe overflow test ++ @echo -------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" +@@ -154,18 +159,20 @@ build_zerowrite_test: test\writezero.obj + link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +189,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -200,9 +208,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +232,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..2a9bb49 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,8 @@ + #include "ld.h" + #include + #include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +21,31 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_args, ++ this->lib_dir_args, this->obj_args})); ++ link_run.Parse(); ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ std::string const rc_file = createRC(link_run.get_out()); ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end ++ this->lib_args.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -99,3 +113,50 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(const std::string& pe_stage_name) { ++ const std::string template_base = ++ "STRINGTABLE\n" ++ "BEGIN\n"; ++ // This string table ID is completely arbitrary, HOWEVER ++ // Spack Core relies on this specific value ++ // If it is changed here, it must be changed in Spack Core ++ const std::string string_table_id = " 59673 "; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectations ++ std::string res_file_name = pe_name + ".res"; ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ return std::string(); ++ } ++ std::string abs_out; ++ if (IsPathAbsolute(pe_stage_name)) { ++ abs_out = pe_stage_name; ++ } else { ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ abs_out = join({GetCWD(), pe_stage_name}, "\\"); ++ } ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char const* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length())); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ rc_out << template_base << string_table_id << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ return std::string(); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..764baf4 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -20,4 +20,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(const std::string& pe_stage_name); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..ef52041 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -84,6 +84,9 @@ void LinkerInvocation::Parse() { + // with no "out" argument, the linker + // will place the file in the CWD + std::string const name_obj = this->objs_.front(); ++ // std::string const filename = ++ // strip(strip(split(name_obj, "\\").back(), ".lib"), ".obj"); ++ // this->output_ = join({GetCWD(), filename}, "\\") + ext; + std::string const filename = split(name_obj, "\\").back(); + this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; + } +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..922785e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->obj_args, this->lib_args})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch deleted file mode 100644 index 508f35b1b00..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rsp.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/src/ld.cxx b/src/ld.cxx -index 7cbe9fb..16a17f2 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -43,6 +43,7 @@ int LdInvocation::InvokeToolchain() - this->rpath_executor = ExecuteCommand("lib.exe", this->ComposeCommandLists( - { - {def_line, "-name:" + dll_name, "-out:"+ abs_out_imp_lib_name}, -+ {link_run.get_rsp_file()}, - this->obj_args, - this->lib_args, - this->lib_dir_args, -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index 8821c8a..1a59fd4 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -190,6 +190,13 @@ void LinkerInvocation::Parse() - StrList defLine = split(*token, ":"); - this->def_file = defLine[1]; - } -+ else if (startswith(normalToken, "@") && endswith(normalToken, ".rsp")) { -+ // RSP files are used to describe object files, libraries, other CLI -+ // Switches relevant to the tool the rsp file is being passed to -+ // Primarily utilized by CMake and MSBuild projects to bypass -+ // Command line length limits -+ this->rsp_file = *token; -+ } - } - std::string ext = this->is_exe ? ".exe" : ".dll"; - if (this->output.empty()){ -@@ -216,6 +223,11 @@ std::string LinkerInvocation::get_def_file() - return this->def_file; - } - -+std::string LinkerInvocation::get_rsp_file() -+{ -+ return this->rsp_file; -+} -+ - std::string LinkerInvocation::get_out() - { - return this->output; -diff --git a/src/winrpath.h b/src/winrpath.h -index 9404a1f..1edb45c 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -215,12 +215,15 @@ public: - std::string get_mangled_out(); - std::string get_implib_name(); - std::string get_def_file(); -+ std::string get_rsp_file(); -+ - private: - std::string line; - StrList tokens; - std::string name; - std::string implibname; - std::string def_file; -+ std::string rsp_file; - std::string output; - StrList libs; - StrList objs; diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch deleted file mode 100644 index 69e7a749bcd..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/spack-installed-libs.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index b026757..764dda6 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -534,6 +534,14 @@ bool hasPathCharacters(const std::string &name) { - return false; - } - -+bool SpackInstalledLib(const std::string &lib) { -+ const std::string prefix = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ if (prefix.empty()) { -+ debug("Unable to determine Spack install prefix, SPACK_INSTALL_PREFIX unset"); -+ return false; -+ } -+ return startswith(lib, prefix); -+} - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} - -diff --git a/src/utils.h b/src/utils.h -index 86a9433..a0accda 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -147,6 +147,8 @@ void replace_path_characters(char in[], int len); - - void replace_special_characters(char in[], int len); - -+bool SpackInstalledLib(const std::string &lib); -+ - // File and File handle helpers // - - // Returns File offset given RVA -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index e19e53f..73fad30 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -829,6 +829,9 @@ bool LibRename::RenameDll(char* name_loc, const std::string &dll_path) - } - } - else { -+ if(SpackInstalledLib(dll_path)) { -+ return true; -+ } - std::string file_name = basename(dll_path); - if(file_name.empty()) { - std::cerr << "Unable to extract filename from dll for relocation" << "\n"; diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch deleted file mode 100644 index 07171edeb50..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/strip_pad.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/src/utils.cxx b/src/utils.cxx -index 764dda6..9f52350 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -490,6 +490,20 @@ int get_padding_length(const std::string &name) - return c; - } - -+std::string strip_padding(const std::string &lib) -+{ -+ // One of the padding characters is a legitimate -+ // path separator -+ int pad_len = get_padding_length(lib)-1; -+ // Capture the drive and drive separator -+ std::string::const_iterator p = lib.cbegin(); -+ std::string::const_iterator e = lib.cbegin()+2; -+ std::string stripped_drive(p, e); -+ e = e + pad_len; -+ std::string path_remainder(e, lib.end()); -+ return stripped_drive + path_remainder; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -540,7 +554,8 @@ bool SpackInstalledLib(const std::string &lib) { - debug("Unable to determine Spack install prefix, SPACK_INSTALL_PREFIX unset"); - return false; - } -- return startswith(lib, prefix); -+ std::string stripped_lib = strip_padding(lib); -+ startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} From 533f861fa2be8496611ec17bc2d4d76700e362b4 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 20 Jan 2026 11:34:27 -0500 Subject: [PATCH 18/81] wip Signed-off-by: John Parent --- .../packages/compiler_wrapper/package.py | 2 +- .../packages/compiler_wrapper/rc_dll_id.patch | 474 ------ .../compiler_wrapper/rc_dll_stage_id.patch | 383 ----- .../compiler_wrapper/rc_updates_11.patch | 1387 +++++++++++++++++ 4 files changed, 1388 insertions(+), 858 deletions(-) delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 990b5e02a6e..0d0d8683b97 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): version("develop", branch="main") with when("@develop platform=windows"): - patch("rc_dll_id.patch") + patch("rc_updates_11.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch deleted file mode 100644 index a64d850c878..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_id.patch +++ /dev/null @@ -1,474 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..1d5b7bd 100644 ---- a/Makefile -+++ b/Makefile -@@ -66,9 +66,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +81,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +95,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,9 +112,10 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -@@ -124,9 +127,10 @@ test_relocate_exe: build_and_check_test_sample - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -142,9 +146,10 @@ test_relocate_dll: build_and_check_test_sample - cd ../.. - - test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Pipe overflow test -+ @echo -------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" -@@ -154,18 +159,20 @@ build_zerowrite_test: test\writezero.obj - link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +189,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -200,9 +208,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +232,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..aded586 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,8 @@ - #include "ld.h" - #include - #include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +21,31 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_args, -+ this->lib_dir_args, this->obj_args})); -+ link_run.Parse(); -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ std::string const rc_file = createRC(link_run.get_out()); -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end -+ this->lib_args.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -99,3 +113,45 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(const std::string& pe_stage_name) { -+ const std::string template_base = -+ "STRINGTABLE\n" -+ "BEGIN\n"; -+ // This string table ID is completely arbitrary, HOWEVER -+ // Spack Core relies on this specific value -+ // If it is changed here, it must be changed in Spack Core -+ const std::string string_table_id = " 59673 "; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectations -+ std::string res_file_name = pe_name + ".res"; -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ return std::string(); -+ } -+ std::string abs_out = -+ EnsureValidLengthPath(MakePathAbsolute(pe_stage_name)); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char const* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ rc_out << template_base << string_table_id << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ return std::string(); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..764baf4 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -20,4 +20,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(const std::string& pe_stage_name); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..ef52041 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -84,6 +84,9 @@ void LinkerInvocation::Parse() { - // with no "out" argument, the linker - // will place the file in the CWD - std::string const name_obj = this->objs_.front(); -+ // std::string const filename = -+ // strip(strip(split(name_obj, "\\").back(), ".lib"), ".obj"); -+ // this->output_ = join({GetCWD(), filename}, "\\") + ext; - std::string const filename = split(name_obj, "\\").back(); - this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; - } -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..922785e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->obj_args, this->lib_args})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..2f8dd68 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -529,7 +529,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,7 +544,7 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; -@@ -623,6 +624,25 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +653,9 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -diff --git a/src/utils.h b/src/utils.h -index b04d929..3462020 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -188,7 +188,8 @@ std::string mangle_name(const std::string& name); - - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +197,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch deleted file mode 100644 index 39aa5531237..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_dll_stage_id.patch +++ /dev/null @@ -1,383 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..1d5b7bd 100644 ---- a/Makefile -+++ b/Makefile -@@ -66,9 +66,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +81,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +95,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,9 +112,10 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -@@ -124,9 +127,10 @@ test_relocate_exe: build_and_check_test_sample - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -142,9 +146,10 @@ test_relocate_dll: build_and_check_test_sample - cd ../.. - - test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Pipe overflow test -+ @echo -------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" -@@ -154,18 +159,20 @@ build_zerowrite_test: test\writezero.obj - link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +189,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -200,9 +208,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +232,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..2a9bb49 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,8 @@ - #include "ld.h" - #include - #include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +21,31 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_args, -+ this->lib_dir_args, this->obj_args})); -+ link_run.Parse(); -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ std::string const rc_file = createRC(link_run.get_out()); -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end -+ this->lib_args.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -99,3 +113,50 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(const std::string& pe_stage_name) { -+ const std::string template_base = -+ "STRINGTABLE\n" -+ "BEGIN\n"; -+ // This string table ID is completely arbitrary, HOWEVER -+ // Spack Core relies on this specific value -+ // If it is changed here, it must be changed in Spack Core -+ const std::string string_table_id = " 59673 "; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectations -+ std::string res_file_name = pe_name + ".res"; -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ return std::string(); -+ } -+ std::string abs_out; -+ if (IsPathAbsolute(pe_stage_name)) { -+ abs_out = pe_stage_name; -+ } else { -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ abs_out = join({GetCWD(), pe_stage_name}, "\\"); -+ } -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char const* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length())); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ rc_out << template_base << string_table_id << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ return std::string(); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..764baf4 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -20,4 +20,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(const std::string& pe_stage_name); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..ef52041 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -84,6 +84,9 @@ void LinkerInvocation::Parse() { - // with no "out" argument, the linker - // will place the file in the CWD - std::string const name_obj = this->objs_.front(); -+ // std::string const filename = -+ // strip(strip(split(name_obj, "\\").back(), ".lib"), ".obj"); -+ // this->output_ = join({GetCWD(), filename}, "\\") + ext; - std::string const filename = split(name_obj, "\\").back(); - this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; - } -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..922785e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->obj_args, this->lib_args})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch new file mode 100644 index 00000000000..71268985cc2 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch @@ -0,0 +1,1387 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..abae347 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,9 +115,10 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +@@ -124,9 +130,10 @@ test_relocate_exe: build_and_check_test_sample + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -141,31 +148,44 @@ test_relocate_dll: build_and_check_test_sample + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..f246136 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,8 @@ + #include "ld.h" + #include + #include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +21,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_args, ++ this->lib_dir_args, this->obj_args})); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->lib_args.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -52,16 +75,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib + this->rpath_executor = + ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ + {def, piped_args, "-name:" + pe_name, + "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, ++ {link_run.get_rsp_files()}, + this->obj_args, + this->lib_args, + this->lib_dir_args, +@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +124,45 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = ++ EnsureValidLengthPath(MakePathAbsolute(pe_stage_name)); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char const* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..86ec83b 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,29 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +81,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +119,70 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::ofstream rsp_out("spack-build.rsp"); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {"spack-build.rsp"}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +194,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +225,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +250,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +261,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +281,26 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; + } + +-std::string LinkerInvocation::get_out() { ++std::string LinkerInvocation::get_out() const { + return this->pe_name_.empty() ? this->output_ : this->pe_name_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..0008731 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,30 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..385afc1 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->obj_args, this->lib_args})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); +@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // Collect include args as we need to ensure Spack + // Includes come first ++ ++ // Collect all CLI args ++ // certain args have implications when defined in certain orders on ++ // the command line ++ // so we need supply those to the linker exactly as they were ++ // we also need to know where we can inject spack libraries and the resource ++ // where they come first but also do not impact binary naming ++ // Command args will impact naming if they contain a def file or /out ++ // regardless of ordering, otherwise naming is determined by ++ // input file order, preserve that + for (char const* const* co = cli; *co; co++) { + std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..f870d11 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -28,11 +29,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +184,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +543,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,7 +558,7 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; +@@ -623,6 +638,46 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, // Output buffer ++ buffer_size, // Size of output buffer in characters ++ wpath.c_str(), // Input path string ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +688,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +734,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -841,9 +887,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ ++ // Ensure token handle is closed when this scope exits ++ std::unique_ptr scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ // Allocate buffer for TOKEN_USER ++ std::vector buffer(buffer_size); ++ PTOKEN_USER token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (!sid_copy) { ++ return nullptr; ++ } ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ ++ return ScopedSid(sid_copy); ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) { ++ return false; ++ } ++ ++ // Wrap raw pointer to ensure cleanup ++ ScopedLocalInfo scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {0}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) { ++ return false; ++ } ++ ++ // Map GENERIC mappings to specific bits because GetEffectiveRightsFromAcl ++ // returns specific bits (e.g., FILE_WRITE_DATA) not GENERIC_WRITE. ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) { ++ return true; ++ } ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) { ++ return true; ++ } ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) { ++ return true; ++ } ++ ++ // Fallback for exact bit matches ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ // 1. Get Current DACL (Snapshot) ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) { ++ return false; ++ } ++ ++ // If caller requested the old SD, transfer ownership. ++ // Otherwise, wrap it locally to free it at end of scope. ++ if (out_old_sd) { ++ *out_old_sd = sd_raw; ++ } ++ ++ // Helper to manage sd_raw if we are NOT returning it to the caller ++ ScopedLocalInfo temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ // Merges the new rule with the existing DACL ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ ++ if (result != ERROR_SUCCESS) { ++ return false; ++ } ++ ++ ScopedLocalInfo scoped_new_dacl(new_dacl); ++ ++ // Apply the new ACL to the file ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted)) { ++ return false; ++ } ++ ++ if (!present) ++ return false; // No DACL to restore ++ ++ DWORD result = ::SetNamedSecurityInfoW( ++ const_cast(file_path.c_str()), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr); ++ ++ return (result == ERROR_SUCCESS); ++} ++ ++ScopedFileAccess::ScopedFileAccess(const std::wstring& file_path, ++ DWORD desired_access) ++ : file_path_(file_path), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ needs_revert_(false) { ++ ++ // 1. Get Current User SID ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to retrieve current user SID"); ++ } ++ ++ // 2. Check existing permissions ++ // If we already have what we need, we simply return (no revert needed). ++ if (FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ return; ++ } ++ ++ // 3. Attempt to Grant Permissions ++ // If this fails, we throw immediately. The destructor will NOT run for a ++ // partially constructed object, but since we use smart pointers (ScopedSid), ++ // no memory leaks will occur. ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant requested permissions"); ++ } ++ ++ // If we got here, we successfully changed the ACL. ++ needs_revert_ = true; ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ if (needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If the constructor finished and we are in 'needs_revert_' state, ++ // we definitely have access. ++ if (needs_revert_) ++ return true; ++ ++ // Otherwise, we perform a live check. ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..35bbe98 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,12 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +206,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +232,23 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) { ++ ::LocalFree(p); ++ } ++ } ++}; ++ ++// Custom deleter for Standard C pointers (malloc/free) used for SIDs. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) { ++ std::free(p); ++ } ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,59 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++using ScopedLocalInfo = std::unique_ptr; ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ // Disallow instantiation ++ FileSecurity() = delete; ++ FileSecurity(const FileSecurity&) = delete; ++ FileSecurity& operator=(const FileSecurity&) = delete; ++ ++ // Gets the SID for the current process user. ++ // Returns a unique_ptr to the SID structure. ++ static ScopedSid GetCurrentUserSid(); ++ ++ // Checks if the specified SID has the requested access rights. ++ // access_mask: The specific rights to check (e.g., GENERIC_WRITE). ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ // Grants the specified permissions to the SID. ++ // If successful, returns true and populates out_old_sd with the original ++ // security descriptor (if provided) so it can be reverted later. ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ // Reverts permissions by applying a saved Security Descriptor. ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++}; ++ ++class ScopedFileAccess { ++ public: ++ // Constructor attempts to grant permission immediately. ++ // Throws std::system_error if permissions cannot be obtained. ++ explicit ScopedFileAccess(const std::wstring& file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ++ ~ScopedFileAccess(); ++ ++ // Returns true if access is currently valid. ++ // (Always true immediately after construction, but useful if external ++ // factors might revoke access). ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool needs_revert_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +358,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..b56338e 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -439,15 +440,22 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " << pe_path.c_str() ++ << ": " << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library From 09aeff2d649818c6a404baad1f3f06491df419c1 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 27 Jan 2026 11:01:42 -0500 Subject: [PATCH 19/81] churn Signed-off-by: John Parent --- .../packages/compiler_wrapper/package.py | 2 +- ...c_updates_11.patch => rc_updates_13.patch} | 111 +- .../compiler_wrapper/rc_updates_14.patch | 1893 +++++++++++++++ .../compiler_wrapper/rc_updates_15.patch | 1942 ++++++++++++++++ .../compiler_wrapper/rc_updates_16.patch | 1948 ++++++++++++++++ .../compiler_wrapper/rc_updates_17.patch | 1991 ++++++++++++++++ .../compiler_wrapper/rc_updates_18.patch | 1986 ++++++++++++++++ .../compiler_wrapper/rc_updates_19.patch | 1987 ++++++++++++++++ .../compiler_wrapper/rc_updates_20.patch | 1988 ++++++++++++++++ .../compiler_wrapper/rc_updates_21.patch | 2034 ++++++++++++++++ .../compiler_wrapper/rc_updates_22.patch | 2047 ++++++++++++++++ .../compiler_wrapper/rc_updates_23.patch | 2042 ++++++++++++++++ .../compiler_wrapper/rc_updates_24.patch | 2055 +++++++++++++++++ .../compiler_wrapper/rc_updates_25.patch | 2055 +++++++++++++++++ 14 files changed, 24057 insertions(+), 24 deletions(-) rename repos/spack_repo/builtin/packages/compiler_wrapper/{rc_updates_11.patch => rc_updates_13.patch} (92%) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 0d0d8683b97..16e7bce5bc8 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): version("develop", branch="main") with when("@develop platform=windows"): - patch("rc_updates_11.patch") + patch("rc_updates_25.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch similarity index 92% rename from repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch rename to repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch index 71268985cc2..2d67e0ef3a0 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_11.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch @@ -286,7 +286,7 @@ index b00bf1d..b4f8376 100644 } diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..f246136 100644 +index 5cc0683..9d6b3dd 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -6,6 +6,8 @@ @@ -307,8 +307,8 @@ index 5cc0683..f246136 100644 + // First parse the linker command line to + // understand what we'll be doing + LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_args, -+ this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); + link_run.Parse(); + std::string rc_file; + try { @@ -324,7 +324,7 @@ index 5cc0683..f246136 100644 + // file the linker sees (or is referenced in the case of an rsp) + // otherwise this resource file will dictate the binairies + // name, which will break client expectations -+ this->lib_args.push_back(rc_file); ++ this->input_files.push_back(rc_file); // Run base linker invocation to produce initial // dll and import library DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); @@ -343,7 +343,7 @@ index 5cc0683..f246136 100644 // We're creating a PE, we need to create an appropriate import lib std::string const imp_lib_name = link_run.get_implib_name(); // If there is no implib, we don't need to bother -@@ -52,16 +75,15 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -52,18 +75,16 @@ DWORD LdInvocation::InvokeToolchain() { return ExitConditions::NORMALIZE_NAME_FAILURE; } std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; @@ -359,11 +359,14 @@ index 5cc0683..f246136 100644 {def, piped_args, "-name:" + pe_name, "-out:" + abs_out_imp_lib_name}, - {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, + {link_run.get_rsp_files()}, - this->obj_args, - this->lib_args, ++ this->input_files, this->lib_dir_args, -@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { + })); + this->rpath_executor.Execute(); +@@ -73,10 +94,13 @@ DWORD LdInvocation::InvokeToolchain() { } CoffReaderWriter coff_reader(abs_out_imp_lib_name); CoffParser coff(&coff_reader); @@ -377,7 +380,7 @@ index 5cc0683..f246136 100644 if (!coff.NormalizeName(pe_name)) { debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); -@@ -99,3 +124,45 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -99,3 +123,45 @@ DWORD LdInvocation::InvokeToolchain() { } return ret_code; } @@ -443,7 +446,7 @@ index a9f3a82..191aaba 100644 + static std::string createRC(LinkerInvocation& link_run); }; diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..86ec83b 100644 +index 4ed82be..2b4b1b0 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -3,14 +3,19 @@ @@ -466,7 +469,7 @@ index 4ed82be..86ec83b 100644 /** * Parses the command line of a given linker invocation and stores information * about that command line and its associated behavior -@@ -46,21 +51,29 @@ void LinkerInvocation::Parse() { +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { // len 2 StrList implib_line = split(*token, ":"); this->implibname_ = implib_line[1]; @@ -500,10 +503,11 @@ index 4ed82be..86ec83b 100644 + this->input_files_.push_back(*token); + } else if (endswith(normal_token, ".res")) { + this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); } else if (startswith(normal_token, "def")) { this->def_file_ = strip(split(*token, ":", 1)[1], "\""); } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +81,37 @@ void LinkerInvocation::Parse() { +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { this->piped_args_.at(normal_token).emplace_back(*token); } } @@ -555,7 +559,7 @@ index 4ed82be..86ec83b 100644 } if (this->implibname_.empty()) { std::string const name = strip(this->output_, ext); -@@ -93,6 +119,70 @@ void LinkerInvocation::Parse() { +@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { } } @@ -626,7 +630,7 @@ index 4ed82be..86ec83b 100644 /** * processDefFile reads a def file passed to the linker * looking for either LIBRARY or NAME keywords -@@ -104,11 +194,15 @@ void LinkerInvocation::Parse() { +@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { */ void LinkerInvocation::processDefFile() { @@ -642,7 +646,7 @@ index 4ed82be..86ec83b 100644 } std::string line; -@@ -131,18 +225,22 @@ void LinkerInvocation::processDefFile() { +@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { if (keyword == "NAME") { this->is_exe_ = true; def_line >> this->pe_name_; @@ -667,7 +671,7 @@ index 4ed82be..86ec83b 100644 const std::string def_name = stem(this->def_file_); const std::string def_path = this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +250,7 @@ void LinkerInvocation::processDefFile() { +@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { if (!def_out) { std::cerr << "Error: could not open output def file: " << rename_def << "\n"; @@ -675,7 +679,7 @@ index 4ed82be..86ec83b 100644 } for (const auto& line : exports) { def_out << line << "\n"; -@@ -162,11 +261,11 @@ void LinkerInvocation::processDefFile() { +@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { def_in.close(); } @@ -689,7 +693,7 @@ index 4ed82be..86ec83b 100644 std::string lib_link_line; for (const auto& var_args : this->piped_args_) { // Most of these should be single arguments -@@ -182,22 +281,26 @@ std::string LinkerInvocation::get_lib_link_args() { +@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { return lib_link_line; } @@ -709,8 +713,9 @@ index 4ed82be..86ec83b 100644 } -std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; +std::string LinkerInvocation::get_out() const { - return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++ return this->output_; } -std::string LinkerInvocation::get_mangled_out() { @@ -769,7 +774,7 @@ index a92a538..0008731 100644 std::map piped_args_ = { {"export", {}}, {"include", {}}, {"libpath", {}}, diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..385afc1 100644 +index a1c103f..87be08e 100644 --- a/src/toolchain.cxx +++ b/src/toolchain.cxx @@ -15,7 +15,7 @@ @@ -781,6 +786,15 @@ index a1c103f..385afc1 100644 this->ParseCommandArgs(cli); } +@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + this->include_args.insert(this->include_args.begin(), inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->input_files.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); @@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { DWORD ToolChainInvocation::InvokeToolchain() { @@ -788,7 +802,7 @@ index a1c103f..385afc1 100644 - {this->command_args, this->include_args, this->lib_args, - this->lib_dir_args, this->obj_args})); + {this->command_args, this->include_args, this->lib_dir_args, -+ this->obj_args, this->lib_args})); ++ this->input_files})); this->executor = ExecuteCommand(this->command, command_line); debug("Setting up executor for " + std::string(typeid(*this).name()) + "toolchain"); @@ -809,6 +823,48 @@ index a1c103f..385afc1 100644 for (char const* const* co = cli; *co; co++) { std::string norm_arg = std::string(*co); const std::string arg = std::string(*co); +@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + this->include_args.push_back(arg); + this->include_args.emplace_back(*(++co)); + } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { ++ } else if ((endswith(norm_arg, ".lib") && ++ (norm_arg.find("implib:") == std::string::npos)) || ++ (endswith(norm_arg, ".obj")) || ++ (endswith(norm_arg, ".res")) || ++ (startswith(norm_arg, "@"))) { ++ // Capture all lib, obj, resource, and rsp files ++ // in the order they're specified since all have binary ++ // naming implications and we don't need finer granularity ++ this->input_files.push_back(arg); ++ } else { + this->command_args.push_back(arg); + } + } +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..5360ead 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -35,7 +35,6 @@ class ToolChainInvocation { + StrList command_args; + StrList include_args; + StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList input_files; + ExecuteCommand executor; + }; diff --git a/src/utils.cxx b/src/utils.cxx index 6bb8c6c..f870d11 100644 --- a/src/utils.cxx @@ -1344,9 +1400,18 @@ index b04d929..35bbe98 100644 + static bool DEBUG = false; diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..b56338e 100644 +index f98e063..73b20fa 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx +@@ -4,7 +4,7 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include ++#include + #include // NOLINT + #include "winrpath.h" + #include @@ -23,6 +23,7 @@ #include #include @@ -1368,7 +1433,7 @@ index f98e063..b56338e 100644 + try { + ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); + HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { + std::cerr << "Unable to acquire file handle to " << pe_path.c_str() diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch new file mode 100644 index 00000000000..bb3ee2f469a --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch @@ -0,0 +1,1893 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..9229bb5 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->input_files.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib + this->rpath_executor = + ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ + {def, piped_args, "-name:" + pe_name, + "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, ++ {link_run.get_rsp_files()}, ++ this->input_files, + this->lib_dir_args, + })); + this->rpath_executor.Execute(); +@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..2b4b1b0 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::ofstream rsp_out("spack-build.rsp"); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {"spack-build.rsp"}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..0008731 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,30 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..87be08e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + this->include_args.insert(this->include_args.begin(), inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->input_files.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); +@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // Collect include args as we need to ensure Spack + // Includes come first ++ ++ // Collect all CLI args ++ // certain args have implications when defined in certain orders on ++ // the command line ++ // so we need supply those to the linker exactly as they were ++ // we also need to know where we can inject spack libraries and the resource ++ // where they come first but also do not impact binary naming ++ // Command args will impact naming if they contain a def file or /out ++ // regardless of ordering, otherwise naming is determined by ++ // input file order, preserve that + for (char const* const* co = cli; *co; co++) { + std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + this->include_args.push_back(arg); + this->include_args.emplace_back(*(++co)); + } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { ++ } else if ((endswith(norm_arg, ".lib") && ++ (norm_arg.find("implib:") == std::string::npos)) || ++ (endswith(norm_arg, ".obj")) || ++ (endswith(norm_arg, ".res")) || ++ (startswith(norm_arg, "@"))) { ++ // Capture all lib, obj, resource, and rsp files ++ // in the order they're specified since all have binary ++ // naming implications and we don't need finer granularity ++ this->input_files.push_back(arg); ++ } else { + this->command_args.push_back(arg); + } + } +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..5360ead 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -35,7 +35,6 @@ class ToolChainInvocation { + StrList command_args; + StrList include_args; + StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList input_files; + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..d7dad08 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // --- PHASE 1: ACL HANDLING --- ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ // --- PHASE 2: ATTRIBUTE HANDLING --- ++ // Now that we (presumably) have write access from Phase 1, ++ // we check for the Read-Only bit. ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // --- REVERT PHASE 1: Restore Attributes --- ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ // --- REVERT PHASE 2: Restore ACLs --- ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..29416db 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,79 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // --- NEW: Attribute Helpers --- ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ // 1. Grants ACL permissions. ++ // 2. Removes Read-Only attribute if present. ++ // Throws std::system_error on failure. ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ++ // 1. Restores original attributes (re-enables Read-Only if needed). ++ // 2. Reverts ACL permissions. ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +378,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch new file mode 100644 index 00000000000..02e729d879a --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch @@ -0,0 +1,1942 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..dda65fa 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,13 +14,28 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + this->pe_stream_.open(this->file_, + std::ios::in | std::ios::out | std::ios::binary); + return this->pe_stream_.is_open(); +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..9229bb5 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->input_files.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib + this->rpath_executor = + ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ + {def, piped_args, "-name:" + pe_name, + "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, ++ {link_run.get_rsp_files()}, ++ this->input_files, + this->lib_dir_args, + })); + this->rpath_executor.Execute(); +@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..2b4b1b0 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::ofstream rsp_out("spack-build.rsp"); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {"spack-build.rsp"}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..0008731 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,30 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..87be08e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + this->include_args.insert(this->include_args.begin(), inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->input_files.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); +@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // Collect include args as we need to ensure Spack + // Includes come first ++ ++ // Collect all CLI args ++ // certain args have implications when defined in certain orders on ++ // the command line ++ // so we need supply those to the linker exactly as they were ++ // we also need to know where we can inject spack libraries and the resource ++ // where they come first but also do not impact binary naming ++ // Command args will impact naming if they contain a def file or /out ++ // regardless of ordering, otherwise naming is determined by ++ // input file order, preserve that + for (char const* const* co = cli; *co; co++) { + std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + this->include_args.push_back(arg); + this->include_args.emplace_back(*(++co)); + } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { ++ } else if ((endswith(norm_arg, ".lib") && ++ (norm_arg.find("implib:") == std::string::npos)) || ++ (endswith(norm_arg, ".obj")) || ++ (endswith(norm_arg, ".res")) || ++ (startswith(norm_arg, "@"))) { ++ // Capture all lib, obj, resource, and rsp files ++ // in the order they're specified since all have binary ++ // naming implications and we don't need finer granularity ++ this->input_files.push_back(arg); ++ } else { + this->command_args.push_back(arg); + } + } +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..5360ead 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -35,7 +35,6 @@ class ToolChainInvocation { + StrList command_args; + StrList include_args; + StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList input_files; + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..d7dad08 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // --- PHASE 1: ACL HANDLING --- ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ // --- PHASE 2: ATTRIBUTE HANDLING --- ++ // Now that we (presumably) have write access from Phase 1, ++ // we check for the Read-Only bit. ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // --- REVERT PHASE 1: Restore Attributes --- ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ // --- REVERT PHASE 2: Restore ACLs --- ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch new file mode 100644 index 00000000000..b1fabddc904 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch @@ -0,0 +1,1948 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..9229bb5 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->input_files.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); ++ + // We're creating a PE, we need to create an appropriate import lib + std::string const imp_lib_name = link_run.get_implib_name(); + // If there is no implib, we don't need to bother +@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib + this->rpath_executor = + ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ + {def, piped_args, "-name:" + pe_name, + "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, ++ {link_run.get_rsp_files()}, ++ this->input_files, + this->lib_dir_args, + })); + this->rpath_executor.Execute(); +@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..2b4b1b0 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::ofstream rsp_out("spack-build.rsp"); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {"spack-build.rsp"}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..0008731 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,30 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..87be08e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + this->include_args.insert(this->include_args.begin(), inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->input_files.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + + DWORD ToolChainInvocation::InvokeToolchain() { + StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); ++ {this->command_args, this->include_args, this->lib_dir_args, ++ this->input_files})); + this->executor = ExecuteCommand(this->command, command_line); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); +@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + // Collect include args as we need to ensure Spack + // Includes come first ++ ++ // Collect all CLI args ++ // certain args have implications when defined in certain orders on ++ // the command line ++ // so we need supply those to the linker exactly as they were ++ // we also need to know where we can inject spack libraries and the resource ++ // where they come first but also do not impact binary naming ++ // Command args will impact naming if they contain a def file or /out ++ // regardless of ordering, otherwise naming is determined by ++ // input file order, preserve that + for (char const* const* co = cli; *co; co++) { + std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { + this->include_args.push_back(arg); + this->include_args.emplace_back(*(++co)); + } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { ++ } else if ((endswith(norm_arg, ".lib") && ++ (norm_arg.find("implib:") == std::string::npos)) || ++ (endswith(norm_arg, ".obj")) || ++ (endswith(norm_arg, ".res")) || ++ (startswith(norm_arg, "@"))) { ++ // Capture all lib, obj, resource, and rsp files ++ // in the order they're specified since all have binary ++ // naming implications and we don't need finer granularity ++ this->input_files.push_back(arg); ++ } else { + this->command_args.push_back(arg); + } + } +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..5360ead 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -35,7 +35,6 @@ class ToolChainInvocation { + StrList command_args; + StrList include_args; + StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList input_files; + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..d7dad08 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // --- PHASE 1: ACL HANDLING --- ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ // --- PHASE 2: ATTRIBUTE HANDLING --- ++ // Now that we (presumably) have write access from Phase 1, ++ // we check for the Read-Only bit. ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // --- REVERT PHASE 1: Restore Attributes --- ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ // --- REVERT PHASE 2: Restore ACLs --- ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch new file mode 100644 index 00000000000..23d443870dc --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch @@ -0,0 +1,1991 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..5a86f82 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename +- if (!fileExists(imp_lib_name)) { ++ // trying to rename or if the imp lib already existed ++ if (!fileExists(imp_lib_name) && created_imp_lib) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not +@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..6f6d0bd 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +120,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +197,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +228,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +253,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +264,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +284,26 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..8dbbfa9 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..b6b7f75 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -9,6 +9,8 @@ + #include "spack_env.h" + #include "utils.h" + ++using StrRefList = std::vector; ++ + /** + * @brief + */ +@@ -32,10 +34,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch new file mode 100644 index 00000000000..b0a9ee9312f --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch @@ -0,0 +1,1986 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..5a86f82 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename +- if (!fileExists(imp_lib_name)) { ++ // trying to rename or if the imp lib already existed ++ if (!fileExists(imp_lib_name) && created_imp_lib) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not +@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..e13e97d 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +120,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +197,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +228,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +253,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +264,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +284,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..8dbbfa9 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch new file mode 100644 index 00000000000..022e312403b --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch @@ -0,0 +1,1987 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..5a86f82 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename +- if (!fileExists(imp_lib_name)) { ++ // trying to rename or if the imp lib already existed ++ if (!fileExists(imp_lib_name) && created_imp_lib) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not +@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..8dbbfa9 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch new file mode 100644 index 00000000000..325d8d53fcd --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch @@ -0,0 +1,1988 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..dbcb708 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..5a86f82 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename +- if (!fileExists(imp_lib_name)) { ++ // trying to rename or if the imp lib already existed ++ if (!fileExists(imp_lib_name) && created_imp_lib) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not +@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch new file mode 100644 index 00000000000..ad905becb8f --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch @@ -0,0 +1,2034 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..a9a3c6b 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,14 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..0143e96 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,7 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..4f69ddc 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,30 +22,75 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename ++ // trying to rename or if the imp lib already existed + if (!fileExists(imp_lib_name)) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not + // the concern of this wrapper +- return 0; ++ if (created_imp_lib) { ++ return 0; ++ } ++ // Check if the imp lib is associated with the link command ++ // we just ran ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ std::string const long_name = existing_coff.GetLongName(); ++ std::string const link_name = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(link_run.get_out()))); ++ if (long_name != link_name) { ++ return 0; ++ } + } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +100,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +116,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +145,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch new file mode 100644 index 00000000000..84efa4403b7 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch @@ -0,0 +1,2047 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..3452b01 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,30 +22,73 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. if it does before we run the toolchain, ++ // another command created it, this command probably ++ // isn't supposed to overwrite it ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ // but if it exists already, likely would not be overwritten ++ // If the file does not exist, this run could be responsible for it ++ bool const created_imp_lib = !fileExists(imp_lib_name); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ + // If there is no implib, we don't need to bother +- // trying to rename +- if (!fileExists(imp_lib_name)) { ++ // trying to rename or if the imp lib already existed ++ if (!fileExists(imp_lib_name) || !created_imp_lib) { + // There are numerous contexts in which a PE file + // may not export symbols, some are bugs in the + // upstream project, most are valid, all are not + // the concern of this wrapper + return 0; + } ++ // Check if the imp lib is associated with the link command ++ // we just ran ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = link_run.get_out(); ++ debug("internal lib name: " + short_name + " Pe name: " + link_name); ++ if (short_name != link_name) { ++ return 0; ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +98,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +114,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +143,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch new file mode 100644 index 00000000000..13d4fad3dd3 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch @@ -0,0 +1,2042 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..3ee24c6 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,18 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = link_run.get_out(); ++ debug("internal lib name: " + short_name + " Pe name: " + link_name); ++ if (short_name != link_name) { ++ return 0; ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +97,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +113,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +142,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch new file mode 100644 index 00000000000..adcf9a901b1 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch @@ -0,0 +1,2055 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..4be86ce 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran, if we cannot process the coff file ++ // we should exit ++ { ++ // Create temp scope to ensure all handles are appropriately deallocated ++ // since the Coff readers use RAII ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ if (!existing_coff.Parse()) { ++ std::cerr << "Unable to parse coff file: " << imp_lib_name ++ << " unable to determine import library provenance\n"; ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = link_run.get_out(); ++ ++ if (short_name != link_name) { ++ debug("internal lib name: " + short_name + ++ " Pe name: " + link_name + " are not equivalent"); ++ return 0; ++ } ++ existing_coff_reader.Close(); ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch new file mode 100644 index 00000000000..030b2b987b1 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch @@ -0,0 +1,2055 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..2f4bc79 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran, if we cannot process the coff file ++ // we should exit ++ { ++ // Create temp scope to ensure all handles are appropriately deallocated ++ // since the Coff readers use RAII ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ if (!existing_coff.Parse()) { ++ std::cerr << "Unable to parse coff file: " << imp_lib_name ++ << " unable to determine import library provenance\n"; ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = basename(link_run.get_out()); ++ ++ if (short_name != link_name) { ++ debug("internal lib name: " + short_name + ++ " Pe name: " + link_name + " are not equivalent"); ++ return 0; ++ } ++ existing_coff_reader.Close(); ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..32da29c 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return this->FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file From ebe2b85f83dfcfd82ca78407cecf2269688627af Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 27 Jan 2026 17:38:52 -0500 Subject: [PATCH 20/81] wip Signed-off-by: John Parent --- .../packages/compiler_wrapper/package.py | 2 +- .../compiler_wrapper/rc_updates_26.patch | 2065 ++++++++++++++++ .../compiler_wrapper/rc_updates_27.patch | 2074 ++++++++++++++++ .../compiler_wrapper/rc_updates_28.patch | 2075 +++++++++++++++++ 4 files changed, 6215 insertions(+), 1 deletion(-) create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch create mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 16e7bce5bc8..3c8cf4bb8ba 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): version("develop", branch="main") with when("@develop platform=windows"): - patch("rc_updates_25.patch") + patch("rc_updates_28.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch new file mode 100644 index 00000000000..528b53a721e --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch @@ -0,0 +1,2065 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..2f4bc79 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran, if we cannot process the coff file ++ // we should exit ++ { ++ // Create temp scope to ensure all handles are appropriately deallocated ++ // since the Coff readers use RAII ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ if (!existing_coff.Parse()) { ++ std::cerr << "Unable to parse coff file: " << imp_lib_name ++ << " unable to determine import library provenance\n"; ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = basename(link_run.get_out()); ++ ++ if (short_name != link_name) { ++ debug("internal lib name: " + short_name + ++ " Pe name: " + link_name + " are not equivalent"); ++ return 0; ++ } ++ existing_coff_reader.Close(); ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..71941bf 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..8466442 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -322,7 +280,8 @@ bool LibRename::ComputeDefFile() { + npos) { // Skip header in export block if still present + break; + } +- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; ++ output_file << " " << regexMatch(line, R"(^\s+(?:\d+\s+)?(\w+))") ++ << '\n'; + } + input_file.close(); + output_file.close(); +@@ -357,7 +316,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +398,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return LibRename::FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch new file mode 100644 index 00000000000..ab84d16ac73 --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch @@ -0,0 +1,2074 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..2f4bc79 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran, if we cannot process the coff file ++ // we should exit ++ { ++ // Create temp scope to ensure all handles are appropriately deallocated ++ // since the Coff readers use RAII ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ if (!existing_coff.Parse()) { ++ std::cerr << "Unable to parse coff file: " << imp_lib_name ++ << " unable to determine import library provenance\n"; ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = basename(link_run.get_out()); ++ ++ if (short_name != link_name) { ++ debug("internal lib name: " + short_name + ++ " Pe name: " + link_name + " are not equivalent"); ++ return 0; ++ } ++ existing_coff_reader.Close(); ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..e40b680 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -337,7 +358,7 @@ std::string regexMatch( + if (!std::regex_match(searchDomain, match, reg, flag)) { + result_str = std::string(); + } else { +- result_str = match.str(); ++ result_str = match.str(1); + } + return result_str; + } +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..8466442 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -322,7 +280,8 @@ bool LibRename::ComputeDefFile() { + npos) { // Skip header in export block if still present + break; + } +- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; ++ output_file << " " << regexMatch(line, R"(^\s+(?:\d+\s+)?(\w+))") ++ << '\n'; + } + input_file.close(); + output_file.close(); +@@ -357,7 +316,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +398,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return LibRename::FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch new file mode 100644 index 00000000000..2f9805f63fb --- /dev/null +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch @@ -0,0 +1,2075 @@ +diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml +index 9979ea1..7c106b1 100644 +--- a/.github/workflows/ci.yaml ++++ b/.github/workflows/ci.yaml +@@ -6,7 +6,6 @@ name: ci + + on: [pull_request, push] + +- + concurrency: + group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} + cancel-in-progress: true +@@ -35,5 +34,5 @@ jobs: + - uses: actions/upload-artifact@v4 + if: always() + with: +- name: tester +- path: tmp/test/tester.exe ++ name: tests ++ path: tmp +diff --git a/.gitignore b/.gitignore +index 6a74ea7..df2004f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -19,4 +19,7 @@ + /.vscode/ + + # ignore patch files +-*.patch +\ No newline at end of file ++*.patch ++ ++.clang-tidy ++.clang-format +\ No newline at end of file +diff --git a/Makefile b/Makefile +index 54646df..e5111b4 100644 +--- a/Makefile ++++ b/Makefile +@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG + BASE_CFLAGS = /EHsc + CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) + LFLAGS = $(BUILD_LINK) $(LINKFLAGS) ++API_LIBS = Shlwapi.lib \ ++Pathcch.lib \ ++Advapi32.lib + + SRCS = cl.obj \ + execute.obj \ +@@ -55,7 +58,7 @@ linker_invocation.obj + all : install test + + cl.exe : $(SRCS) +- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe + + install : cl.exe + mkdir $(PREFIX) +@@ -66,9 +69,10 @@ install : cl.exe + mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe + + setup_test: cl.exe +- echo "-------------------" +- echo "Running Test Setup" +- echo "-------------------" ++ @echo \n ++ @echo ------------------- ++ @echo Running Test Setup ++ @echo ------------------- + -@ if NOT EXIST "tmp\test" mkdir "tmp\test" + cd tmp\test + copy ..\..\cl.exe cl.exe +@@ -80,9 +84,9 @@ setup_test: cl.exe + # * space in a path - preserved by quoted arguments + # * escaped quoted arguments + build_and_check_test_sample : setup_test +- echo "--------------------" +- echo "Building Test Sample" +- echo "--------------------" ++ @echo -------------------- ++ @echo Building Test Sample ++ @echo -------------------- + cd tmp\test + cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include + cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include +@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test + # Test basic wrapper behavior - did the absolute path to the DLL get injected + # into the executable + test_wrapper : build_and_check_test_sample +- echo "--------------------" +- echo "Running Wrapper Test" +- echo "--------------------" ++ @echo \n ++ @echo -------------------- ++ @echo Running Wrapper Test ++ @echo -------------------- + cd tmp + move test\tester.exe .\tester.exe + .\tester.exe +@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample + + # Test relocating an executable - re-write internal paths to dlls + test_relocate_exe: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate Exe Test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate Exe Test ++ @echo -------------------------- + cd tmp\test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + move calc.dll ..\calc.dll +- relocate.exe --pe tester.exe --deploy --full +- relocate.exe --pe tester.exe --export --full ++ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll ++ relocate.exe --pe tester.exe --full + tester.exe + move ..\calc.dll calc.dll + cd ../.. + + # Test relocating a dll - re-write import library + test_relocate_dll: build_and_check_test_sample +- echo "--------------------------" +- echo "Running Relocate DLL test" +- echo "--------------------------" ++ @echo \n ++ @echo -------------------------- ++ @echo Running Relocate DLL test ++ @echo -------------------------- + cd tmp/test + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample + mkdir tmp_lib + move test\calc.dll tmp_bin\calc.dll + move test\calc.lib tmp_lib\calc.lib +- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export ++ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib + cd test + del tester.exe + link main.obj ..\tmp_lib\calc.lib /out:tester.exe + .\tester.exe + cd ../.. + +-test_pipe_overflow: build_and_check_test_sample +- echo "--------------------" +- echo " Pipe overflow test" +- echo "--------------------" ++test_pipe_out_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stdout overflow test ++ @echo --------------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat + cl /c /EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + ++test_pipe_error_overflow: build_and_check_test_sample ++ @echo \n ++ @echo --------------------------- ++ @echo Pipe stderr overflow test ++ @echo --------------------------- ++ set SPACK_CC_TMP=%SPACK_CC% ++ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat ++ cl /c /EHsc "test\src file\calc.cxx" ++ set SPACK_CC=%SPACK_CC_TMP% ++ + build_zerowrite_test: test\writezero.obj +- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe ++ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe + + test_zerowrite: build_zerowrite_test +- echo "-----------------------" +- echo "Running zerowrite test" +- echo "-----------------------" ++ @echo \n ++ @echo ----------------------- ++ @echo Running zerowrite test ++ @echo ----------------------- + set SPACK_CC_TMP=%SPACK_CC% + set SPACK_CC=$(MAKEDIR)\writezero.exe + cl /c EHsc "test\src file\calc.cxx" + set SPACK_CC=%SPACK_CC_TMP% + + test_long_paths: build_and_check_test_sample +- echo "------------------------" +- echo "Running long paths test" +- echo "------------------------" ++ @echo \n ++ @echo ------------------------ ++ @echo Running long paths test ++ @echo ------------------------ + mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname +@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample + cd ../../../.. + + test_relocate_long_paths: test_long_paths +- echo "---------------------------------" +- echo "Running relocate logn paths test" +- echo "---------------------------------" ++ @echo \n ++ @echo --------------------------------- ++ @echo Running relocate logn paths test ++ @echo --------------------------------- + cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname + -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe + cd .. +@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths + mkdir tmp_lib + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll + move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib +- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export ++ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib + cd evenlongersubdirectoryname + del tester.exe + link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe +@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths + cd ../../../.. + + test_exe_with_exports: +- echo ------------------------------ +- echo Running exe with exports test +- echo ------------------------------ ++ @echo \n ++ @echo ------------------------------ ++ @echo Running exe with exports test ++ @echo ------------------------------ + mkdir tmp\test\exe_with_exports + xcopy /E test\include tmp\test\exe_with_exports + xcopy /E "test\src file" tmp\test\exe_with_exports +@@ -223,6 +245,10 @@ test_exe_with_exports: + cd ../../.. + + test_def_file_name_override: ++ @echo ++ @echo ------------------------------------ ++ @echo Running Def file name override test ++ @echo ------------------------------------ + mkdir tmp\test\def\def_override + xcopy /E test\include tmp\test\def\def_override + xcopy /E "test\src file" tmp\test\def\def_override +@@ -241,7 +267,7 @@ test_def_file_name_override: + test_and_cleanup: test clean-test + + +-test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow ++test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow + + + clean : clean-test clean-cl +diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx +index 25f4b28..4c1fb4d 100644 +--- a/src/coff_parser.cxx ++++ b/src/coff_parser.cxx +@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) + */ + bool CoffParser::Parse() { + if (!this->coffStream_->Open()) { +- std::cerr << "Unable to open coff file for reading: " +- << reportLastError() << "\n"; ++ std::cerr << "Unable to open coff file: " ++ << this->coffStream_->get_file() ++ << " for reading: " << reportLastError() << "\n"; + return false; + } + int const invalid_valid_sig = +@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { + std::cout << "DLL: " << data << "\n"; + } + ++std::string CoffParser::GetLongName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.member->is_longname) { ++ return std::string(mem.member->data); ++ } ++ } ++} ++ ++std::string CoffParser::GetShortName() { ++ for (auto mem : this->coff_.members) { ++ if (mem.header->Name[0] != '/') { ++ int i = 0; ++ while (mem.header->Name[i] != '/') { ++ ++i; ++ } ++ return std::string(reinterpret_cast(mem.header->Name), ++ i); ++ } ++ } ++ return std::string(); ++} ++ + void CoffParser::Report() { + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { +diff --git a/src/coff_parser.h b/src/coff_parser.h +index 6cf7728..4efe78e 100644 +--- a/src/coff_parser.h ++++ b/src/coff_parser.h +@@ -43,6 +43,8 @@ class CoffParser { + bool NormalizeName(std::string& name); + void Report(); + int Verify(); ++ std::string GetLongName(); ++ std::string GetShortName(); + static int Validate(std::string& coff); + }; + +diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx +index cbab86f..6e34736 100644 +--- a/src/coff_reader_writer.cxx ++++ b/src/coff_reader_writer.cxx +@@ -6,6 +6,7 @@ + + #include "coff_reader_writer.h" + #include "coff.h" ++#include "utils.h" + + #include + #include +@@ -13,16 +14,31 @@ + #include + #include + #include ++#include + #include ++#include + #include + + CoffReaderWriter::CoffReaderWriter(std::string const& file) + : file_(std::move(file)) {} + + bool CoffReaderWriter::Open() { +- this->pe_stream_.open(this->file_, +- std::ios::in | std::ios::out | std::ios::binary); +- return this->pe_stream_.is_open(); ++ std::wstring coff_file; ++ try { ++ coff_file = ConvertASCIIToWide(this->file_); ++ } catch (std::overflow_error& e) { ++ return false; ++ } ++ try { ++ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); ++ this->pe_stream_.open(this->file_, ++ std::ios::in | std::ios::out | std::ios::binary); ++ return this->pe_stream_.is_open(); ++ } catch (std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; ++ return false; ++ } + } + + bool CoffReaderWriter::Close() { +diff --git a/src/commandline.cxx b/src/commandline.cxx +index 38bd760..df2bff1 100644 +--- a/src/commandline.cxx ++++ b/src/commandline.cxx +@@ -86,14 +86,6 @@ bool print_help() { + "said library is regenerated and the old imp lib\n"; + std::cout << " " + "replaced.\n"; +- std::cout << " --export|--deploy = " +- "Mutually exclusive command modifier.\n"; +- std::cout << " " +- "Instructs relocate to either prepare the\n"; +- std::cout << " " +- "dynamic library for exporting to build cache\n"; +- std::cout << " " +- "or for extraction from bc onto new host system\n"; + std::cout << " --report = " + "Report information about the parsed PE/Coff files\n"; + std::cout << " --debug|-d = " +@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { + return opts; + } + opts.insert(std::pair("full", "full")); +- } else if (!strcmp(args[i], "--export")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "export")); +- } else if (!strcmp(args[i], "--deploy")) { +- // export and deploy are mutually exclusive, if one is defined +- // the other cannot be +- if (redefinedArgCheck(opts, "export", "--export") || +- redefinedArgCheck(opts, "deploy", "--deploy")) { +- opts.clear(); +- return opts; +- } +- opts.insert(std::pair("cmd", "deploy")); + } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { + opts.insert(std::pair("debug", "on")); + } else if (!strcmp(args[i], "--verify")) { +diff --git a/src/execute.cxx b/src/execute.cxx +index b00bf1d..b4f8376 100644 +--- a/src/execute.cxx ++++ b/src/execute.cxx +@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; + ExecuteCommand::ExecuteCommand(std::string command) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(command)) { + this->CreateChildPipes(); + this->SetupExecute(); +@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) + ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) + : ChildStdOut_Rd(nullptr), + ChildStdOut_Wd(nullptr), ++ ChildStdErr_Rd(nullptr), ++ ChildStdErr_Wd(nullptr), + base_command(std::move(arg)) { + for (const auto& argp : args) { + this->command_args.push_back(argp); +@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( + ExecuteCommand&& execute_command) noexcept { + this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); + this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); ++ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); ++ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); + this->procInfo = std::move(execute_command.procInfo); + this->startInfo = std::move(execute_command.startInfo); + this->saAttr = std::move(execute_command.saAttr); +@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( + this->base_command = std::move(execute_command.base_command); + this->command_args = std::move(execute_command.command_args); + this->child_out_future = std::move(execute_command.child_out_future); ++ this->child_err_future = std::move(execute_command.child_err_future); + this->exit_code_future = std::move(exit_code_future); + return *this; + } +@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { + // This structure specifies the STDIN and STDOUT handles for redirection. + ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); + si_start_info.cb = sizeof(STARTUPINFOW); +- si_start_info.hStdError = this->ChildStdOut_Wd; ++ si_start_info.hStdError = this->ChildStdErr_Wd; + si_start_info.hStdOutput = this->ChildStdOut_Wd; + si_start_info.dwFlags |= STARTF_USESTDHANDLES; + this->procInfo = pi_proc_info; +@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { + // determine when child proc is done + free(nc_command_line); + CloseHandle(this->ChildStdOut_Wd); ++ CloseHandle(this->ChildStdErr_Wd); + return true; + } + +diff --git a/src/ld.cxx b/src/ld.cxx +index 5cc0683..0131acb 100644 +--- a/src/ld.cxx ++++ b/src/ld.cxx +@@ -6,6 +6,9 @@ + #include "ld.h" + #include + #include ++#include ++#include ++#include + #include + #include "coff_parser.h" + #include "coff_reader_writer.h" +@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { + } + + DWORD LdInvocation::InvokeToolchain() { ++ // Run a pass of the linker ++ ++ // First parse the linker command line to ++ // understand what we'll be doing ++ LinkerInvocation link_run(this->inputs); ++ link_run.Parse(); ++ std::string rc_file; ++ try { ++ // Run resource compiler to create ++ // Resource for id'ing binary when relocating its import library ++ rc_file = LdInvocation::createRC(link_run); ++ } catch (const RCCompilerFailure& e) { ++ return ExitConditions::TOOLCHAIN_FAILURE; ++ } ++ ++ // Add produced RC file to linker CLI to inject ID ++ // This needs to be at the end of either libs or objs or rsp files ++ // so long as this RC file is not the first binary ++ // file the linker sees (or is referenced in the case of an rsp) ++ // otherwise this resource file will dictate the binairies ++ // name, which will break client expectations ++ this->inputs.push_back(rc_file); + // Run base linker invocation to produce initial + // dll and import library + DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); + if (ret_code != 0) { + return ret_code; + } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); ++ + // Next we want to construct the proper commmand line to + // recreate the import library from the same set of obj files + // and libs +- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- link_run.Parse(); +- // We're creating a PE, we need to create an appropriate import lib +- std::string const imp_lib_name = link_run.get_implib_name(); ++ ++ // first determine if this link run created the import library ++ // check if the import library that *might* be produced ++ // by this run (given input argument construction) ++ // exists. Multiple link runs could in theory produce the name ++ // imp lib (or at least with the same name) ++ // i.e. link /out:perl.exe perl.lib ++ // and link /out:perl.dll perl.lib /DLL could both in theory ++ // produce the same import library ++ + // If there is no implib, we don't need to bother + // trying to rename + if (!fileExists(imp_lib_name)) { +@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { + // the concern of this wrapper + return 0; + } ++ // There is an imp lib, so ++ // Check if the imp lib is associated with the link command ++ // we just ran, if we cannot process the coff file ++ // we should exit with failure since something is unexpected ++ { ++ // Create temp scope to ensure all handles are appropriately deallocated ++ // since the Coff readers use RAII ++ CoffReaderWriter existing_coff_reader(imp_lib_name); ++ CoffParser existing_coff(&existing_coff_reader); ++ if (!existing_coff.Parse()) { ++ std::cerr << "Unable to parse coff file: " << imp_lib_name ++ << " unable to determine import library provenance\n"; ++ return ExitConditions::COFF_PARSE_FAILURE; ++ } ++ std::string const short_name = existing_coff.GetShortName(); ++ std::string const link_name = basename(link_run.get_out()); ++ ++ if (short_name != link_name) { ++ debug("internal lib name: " + short_name + ++ " Pe name: " + link_name + " are not equivalent"); ++ return 0; ++ } ++ existing_coff_reader.Close(); ++ } ++ + std::string pe_name; + try { + pe_name = link_run.get_mangled_out(); +@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { + return ExitConditions::NORMALIZE_NAME_FAILURE; + } + std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; +- std::string const def_file = +- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); +- std::string const def = "-def" + def_file; ++ std::string const def_file = link_run.get_def_file(); ++ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); + std::string piped_args = link_run.get_lib_link_args(); + // create command line to generate new import lib +- this->rpath_executor = +- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ +- {def, piped_args, "-name:" + pe_name, +- "-out:" + abs_out_imp_lib_name}, +- {link_run.get_rsp_file()}, +- this->obj_args, +- this->lib_args, +- this->lib_dir_args, +- })); ++ this->rpath_executor = ExecuteCommand( ++ "lib.exe", ++ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, ++ "-out:" + abs_out_imp_lib_name}, ++ link_run.get_input_files()})); + this->rpath_executor.Execute(); + DWORD const err_code = this->rpath_executor.Join(); + if (err_code != 0) { +@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { + } + CoffReaderWriter coff_reader(abs_out_imp_lib_name); + CoffParser coff(&coff_reader); ++ debug("Parsing COFF file: " + abs_out_imp_lib_name); + if (!coff.Parse()) { + debug("Failed to parse COFF file: " + abs_out_imp_lib_name); + return ExitConditions::COFF_PARSE_FAILURE; + } ++ debug("COFF file parsed"); ++ debug("Normalizing coff file for name: " + pe_name); + if (!coff.NormalizeName(pe_name)) { + debug("Failed to normalize name for COFF file: " + + abs_out_imp_lib_name); +@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { + } + return ret_code; + } ++ ++std::string LdInvocation::createRC(LinkerInvocation& link_run) { ++ const std::string pe_stage_name = link_run.get_out(); ++ const std::string template_base = ++ "spack SPACKRESOURCE\n" ++ "BEGIN\n"; ++ const std::string template_end = "END\n"; ++ const std::string pe_name = stripLastExt(basename(pe_stage_name)); ++ const std::string rc_file_name = "spack-" + pe_name + ".rc"; ++ // This res file name needs to mirror the PE name _exactly_ ++ // Otherwise the RC file will override the default ++ // or user set name, violating user expectation ++ std::string res_file_name = pe_name + ".res"; ++ if (!link_run.get_rc_files().empty()) { ++ res_file_name = "spack-" + res_file_name; ++ } ++ ++ ExecuteCommand rc_executor("rc", ++ {"/fo" + res_file_name + " " + rc_file_name}); ++ std::ofstream rc_out(rc_file_name); ++ if (!rc_out) { ++ std::cerr << "Error: could not open rc file for creation: " ++ << rc_file_name << "\n"; ++ throw RCCompilerFailure("Could not open RC file"); ++ } ++ std::string abs_out = EnsureValidLengthPath( ++ CannonicalizePath(MakePathAbsolute(pe_stage_name))); ++ char* chr_abs_out = new char[abs_out.length() + 1]; ++ strcpy(chr_abs_out, abs_out.c_str()); ++ char* padded_path = ++ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); ++ abs_out = std::string(padded_path, MAX_NAME_LEN); ++ free(chr_abs_out); ++ free(padded_path); ++ abs_out = escape_backslash(abs_out); ++ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" ++ << template_end; ++ rc_out.close(); ++ rc_executor.Execute(); ++ DWORD const err_code = rc_executor.Join(); ++ if (err_code != 0) { ++ throw RCCompilerFailure("Could not compile RC file"); ++ } ++ return res_file_name; ++} +diff --git a/src/ld.h b/src/ld.h +index a9f3a82..191aaba 100644 +--- a/src/ld.h ++++ b/src/ld.h +@@ -5,6 +5,8 @@ + */ + #pragma once + ++#include "linker_invocation.h" ++ + #include "toolchain.h" + + /** +@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { + void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); + std::string lang = "link"; + ExecuteCommand rpath_executor; ++ static std::string createRC(LinkerInvocation& link_run); + }; +diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx +index 4ed82be..328c489 100644 +--- a/src/linker_invocation.cxx ++++ b/src/linker_invocation.cxx +@@ -3,14 +3,19 @@ + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ ++#include + #include + #include + #include + #include + #include ++#include + #include "linker_invocation.h" ++#include + #include "utils.h" + ++enum { MaxProcessCommandLength = 32767 }; ++ + /** + * Parses the command line of a given linker invocation and stores information + * about that command line and its associated behavior +@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { + // len 2 + StrList implib_line = split(*token, ":"); + this->implibname_ = implib_line[1]; +- } else if (endswith(normal_token, ".lib")) { +- this->libs_.push_back(*token); + } else if (normal_token == "dll") { + this->is_exe_ = false; + } else if (startswith(normal_token, "out")) { + this->output_ = split(*token, ":")[1]; +- } else if (endswith(normal_token, ".obj")) { +- this->objs_.push_back(*token); +- } else if (startswith(normal_token, "@") && +- endswith(normal_token, ".rsp")) { ++ } else if (endswith(normal_token, ".obj") || ++ endswith(normal_token, ".lib") || ++ endswith(normal_token, ".lo")) { ++ this->input_files_.push_back(*token); ++ } else if (startswith(normal_token, "@")) { + // RSP files are used to describe object files, libraries, other CLI + // Switches relevant to the tool the rsp file is being passed to + // Primarily utilized by CMake and MSBuild projects to bypass + // Command line length limits +- this->rsp_file_ = *token; ++ this->rsp_files_.push_back(*token); ++ // Since rsp files are essentially expanded in place on the command line ++ // i.e objA rspA objC ++ // where rspA defines objB the cli would then be ++ // objA objB objC ++ // so we also need to track them in binary_files_ since the order ++ // of their expansion has implications for naming, i.e ++ // if rspA was the first input file, the dll/imp name would be objB ++ this->input_files_.push_back(*token); ++ } else if (endswith(normal_token, ".res")) { ++ this->rc_files_.push_back(*token); ++ this->input_files_.push_back(*token); + } else if (startswith(normal_token, "def")) { + this->def_file_ = strip(split(*token, ":", 1)[1], "\""); + } else if (this->piped_args_.find(normal_token) != +@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { + this->piped_args_.at(normal_token).emplace_back(*token); + } + } +- // If we have a def file and no name, attempt to +- // scrape the def file for a name to be sure +- // we respect the intended project name +- // vs overriding via the CLI +- if (!this->def_file_.empty() && this->output_.empty()) { +- this->processDefFile(); +- } ++ ++ // Note for the below: name will never be specified so we only have ++ // /out, .def files, and input files ++ // To determine internal dll name ++ // If a def file was not specified: ++ // /name is used ++ // if no /name /out is used ++ // if no /out or /name use first input file ++ ++ // If a def file was specified: ++ // LIBRARY ++ // otherwise fallback to previous ++ ++ // To determine output name ++ // /OUT is always overriding ++ // If not /OUT and .def file: ++ // LIBRARY ++ // if no def or no LIBRARY ++ // /NAME ++ // if no /NAME ++ // first input file (post rc expanion) ++ ++ this->processDefFile(); ++ this->processInputFiles(); + std::string const ext = this->is_exe_ ? ".exe" : ".dll"; +- // If output wasn't defined on the command line +- // or the def file +- // compute it based on the same logic as the linker +- // i.e. first obj file name + if (this->output_.empty()) { + // with no "out" argument, the linker + // will place the file in the CWD +- std::string const name_obj = this->objs_.front(); +- std::string const filename = split(name_obj, "\\").back(); +- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; ++ std::string const name_component = this->input_files_.front(); ++ std::string const filename = split(name_component, "\\").back(); ++ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; + } + if (this->implibname_.empty()) { + std::string const name = strip(this->output_, ext); +@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + } + } + ++void LinkerInvocation::processInputFiles() { ++ StrList new_input_files; ++ for (auto input = this->input_files_.begin(); ++ input != this->input_files_.end(); ++input) { ++ if (startswith(*input, "@")) { ++ // rsp file - expand contents in input files ++ // list in place and remove self ++ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); ++ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), ++ rsp_inputs.end()); ++ } else { ++ new_input_files.push_back(*input); ++ } ++ } ++ this->input_files_ = new_input_files; ++} ++ ++StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { ++ std::string const rsp_file_in = lstrip(rsp_file, "@"); ++ std::ifstream rsp_stream(rsp_file_in); ++ if (!rsp_stream) { ++ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in ++ << "\n"; ++ throw FileIOError("Cannot open rsp input file: " + GetLastError()); ++ } ++ StrList inputs; ++ std::string line; ++ while (std::getline(rsp_stream, line)) { ++ std::stringstream rsp_line(line); ++ std::string input_file; ++ rsp_line >> input_file; ++ inputs.push_back(input_file); ++ } ++ return inputs; ++} ++ ++/** ++ * \brief Ensure command line given to lib.exe is of appropriate length ++ * max windows createProcess command line length is 32,767, so if we exceed ++ * that, compose all input file args into an rsp. ++ * ++ * Writes an rsp file named spack-build.rsp and sets it to be the only ++ * input file for the lib tool ++ */ ++bool LinkerInvocation::makeRsp() { ++ int const total_length = std::accumulate( ++ this->input_files_.begin(), this->input_files_.end(), 0, ++ [](size_t sum, const std::string& s) { return sum + s.size(); }); ++ if (total_length > MaxProcessCommandLength) { ++ std::string const rsp_name = "spack-build.rsp"; ++ std::ofstream rsp_out(rsp_name); ++ if (!rsp_out) { ++ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; ++ throw FileIOError("Unable to open lib rsp file"); ++ } ++ for (const auto& line : this->input_files_) { ++ rsp_out << line << "\n"; ++ } ++ rsp_out.close(); ++ this->input_files_ = {rsp_name}; ++ this->rsp_files_ = {rsp_name}; ++ return true; ++ } ++ return false; ++} ++ + /** + * processDefFile reads a def file passed to the linker + * looking for either LIBRARY or NAME keywords +@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { + */ + void LinkerInvocation::processDefFile() { + ++ if (this->def_file_.empty()) { ++ return; ++ } + // Def from link line + std::ifstream def_in(this->def_file_); + if (!def_in) { + std::cerr << "Error: Could not open input def file: " << this->def_file_ + << "\n"; ++ throw FileIOError("Cannot open def input file: " + GetLastError()); + } + + std::string line; +@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { + if (keyword == "NAME") { + this->is_exe_ = true; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".exe"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; + def_file_export_name = true; + } else if (keyword == "LIBRARY") { + this->is_exe_ = false; + def_line >> this->pe_name_; +- this->pe_name_ = this->pe_name_ + ".dll"; ++ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; + def_file_export_name = true; + } else { + exports.push_back(line); + } + } + if (def_file_export_name) { ++ // if output is not specified on the command line, this defines the output name ++ if (this->output_.empty()) { ++ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); ++ } + const std::string def_name = stem(this->def_file_); + const std::string def_path = + this->def_file_.substr(0, this->def_file_.find(def_name)); +@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { + if (!def_out) { + std::cerr << "Error: could not open output def file: " << rename_def + << "\n"; ++ throw FileIOError("Cannot open def output file: " + GetLastError()); + } + for (const auto& line : exports) { + def_out << line << "\n"; +@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { + def_in.close(); + } + +-std::string LinkerInvocation::get_implib_name() { ++std::string LinkerInvocation::get_implib_name() const { + return this->implibname_; + } + +-std::string LinkerInvocation::get_lib_link_args() { ++std::string LinkerInvocation::get_lib_link_args() const { + std::string lib_link_line; + for (const auto& var_args : this->piped_args_) { + // Most of these should be single arguments +@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { + return lib_link_line; + } + +-std::string LinkerInvocation::get_def_file() { ++std::string LinkerInvocation::get_def_file() const { + return this->def_file_; + } + +-std::string LinkerInvocation::get_rsp_file() { +- return this->rsp_file_; ++StrList LinkerInvocation::get_rsp_files() const { ++ return this->rsp_files_; ++} ++ ++StrList LinkerInvocation::get_rc_files() const { ++ return this->rc_files_; ++} ++ ++StrList LinkerInvocation::get_input_files() const { ++ return this->input_files_; + } + +-std::string LinkerInvocation::get_out() { +- return this->pe_name_.empty() ? this->output_ : this->pe_name_; ++std::string LinkerInvocation::get_out() const { ++ return this->output_; + } + +-std::string LinkerInvocation::get_mangled_out() { ++std::string LinkerInvocation::get_mangled_out() const { + return mangle_name(this->get_out()); + } + +-bool LinkerInvocation::IsExeLink() { ++bool LinkerInvocation::IsExeLink() const { + return this->is_exe_ || endswith(this->get_out(), ".exe"); + } +diff --git a/src/linker_invocation.h b/src/linker_invocation.h +index a92a538..207feb0 100644 +--- a/src/linker_invocation.h ++++ b/src/linker_invocation.h +@@ -15,25 +15,31 @@ class LinkerInvocation { + explicit LinkerInvocation(const StrList& linkline); + ~LinkerInvocation() = default; + void Parse(); +- bool IsExeLink(); +- std::string get_out(); +- std::string get_mangled_out(); +- std::string get_implib_name(); +- std::string get_def_file(); +- std::string get_rsp_file(); +- std::string get_lib_link_args(); ++ bool IsExeLink() const; ++ std::string get_out() const; ++ std::string get_mangled_out() const; ++ std::string get_implib_name() const; ++ std::string get_def_file() const; ++ StrList get_rsp_files() const; ++ StrList get_rc_files() const; ++ StrList get_input_files() const; ++ std::string get_lib_link_args() const; ++ bool makeRsp(); + + private: + void processDefFile(); ++ void processInputFiles(); ++ static StrList processRSPFile(std::string const& rsp_file); + std::string line_; +- StrList tokens_; + std::string pe_name_; + std::string implibname_; + std::string def_file_; +- std::string rsp_file_; + std::string output_; +- StrList libs_; +- StrList objs_; ++ StrList rsp_files_; ++ StrList rc_files_; ++ StrList command_files_; ++ StrList input_files_; ++ StrList tokens_; + bool is_exe_; + std::map piped_args_ = { + {"export", {}}, {"include", {}}, {"libpath", {}}, +diff --git a/src/main.cxx b/src/main.cxx +index 1fb0b63..7fa7708 100644 +--- a/src/main.cxx ++++ b/src/main.cxx +@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { + return -1; + } + bool const full = !(patch_args.find("full") == patch_args.end()); +- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && +- patch_args.at("cmd") == "deploy"; + bool const report = !(patch_args.find("report") == patch_args.end()); + bool const has_pe = !(patch_args.find("pe") == patch_args.end()); + bool const debug = !(patch_args.find("debug") == patch_args.end()); +@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { + std::unique_ptr rpath_lib; + try { + if (has_coff) { +- rpath_lib = std::make_unique(patch_args.at("pe"), +- patch_args.at("coff"), +- full, deploy, true); ++ rpath_lib = std::make_unique( ++ patch_args.at("pe"), patch_args.at("coff"), full, true); + } else { + rpath_lib = std::make_unique(patch_args.at("pe"), +- full, deploy, true); ++ full, true); + } + } catch (const NameTooLongError& e) { + std::cerr << "Cannot Rename PE file " << patch_args.at("pe") +@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { + } + if (report_args.find("pe") != report_args.end()) { + try { +- LibRename portable_executable( +- report_args.at("pe"), std::string(), false, false, true); ++ LibRename portable_executable(report_args.at("pe"), ++ std::string(), false, true); + portable_executable.ExecuteRename(); + } catch (const NameTooLongError& e) { + std::cerr +diff --git a/src/toolchain.cxx b/src/toolchain.cxx +index a1c103f..1d3318e 100644 +--- a/src/toolchain.cxx ++++ b/src/toolchain.cxx +@@ -15,7 +15,7 @@ + + ToolChainInvocation::ToolChainInvocation(std::string command, + char const* const* cli) +- : command(std::move(std::move(command))) { ++ : command(std::move(command)) { + this->ParseCommandArgs(cli); + } + +@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + // inject Spack includes before the default includes + for (auto& include : spackenv.SpackIncludeDirs) { + auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); +- this->include_args.insert(this->include_args.begin(), inc_arg); ++ this->inputs.push_back(inc_arg); + } + for (auto& lib : spackenv.SpackLdLibs) { +- this->lib_args.push_back(lib); ++ this->inputs.push_back(lib); + } + this->AddExtraLibPaths(spackenv.SpackLinkDirs); + this->AddExtraLibPaths(spackenv.SpackRPathDirs); +@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { + } + + DWORD ToolChainInvocation::InvokeToolchain() { +- StrList const command_line(ToolChainInvocation::ComposeCommandLists( +- {this->command_args, this->include_args, this->lib_args, +- this->lib_dir_args, this->obj_args})); +- this->executor = ExecuteCommand(this->command, command_line); ++ quoteList(this->inputs); ++ this->executor = ExecuteCommand(this->command, this->inputs); + debug("Setting up executor for " + std::string(typeid(*this).name()) + + "toolchain"); + debug("Toolchain: " + this->command); +@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { + } + + void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { +- // Collect include args as we need to ensure Spack +- // Includes come first + for (char const* const* co = cli; *co; co++) { +- std::string norm_arg = std::string(*co); + const std::string arg = std::string(*co); +- lower(norm_arg); +- if (isCommandArg(norm_arg, "i")) { +- // We have an include arg +- // can have an optional space +- // check if there are characters after +- // "/I" and if not we consider the next +- // argument to be the include +- if (arg.size() > 2) +- this->include_args.push_back(arg); +- else { +- this->include_args.push_back(arg); +- this->include_args.emplace_back(*(++co)); +- } +- } else if (endswith(norm_arg, ".lib") && +- (norm_arg.find("implib:") == std::string::npos)) +- // Lib args are just libraries +- // provided like system32.lib on the +- // command line. +- // lib specification order does not matter +- // on MSVC but this is useful for filtering system libs +- // and adding all libs +- this->lib_args.push_back(arg); +- else if (endswith(norm_arg, ".obj")) +- this->obj_args.push_back(arg); +- else { +- this->command_args.push_back(arg); +- } ++ this->inputs.push_back(arg); + } + } + +@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { + + void ToolChainInvocation::AddExtraLibPaths(StrList paths) { + for (auto& lib_dir : paths) { +- this->lib_dir_args.push_back( +- ToolChainInvocation::ComposeLibPathArg(lib_dir)); ++ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); + } + } + +diff --git a/src/toolchain.h b/src/toolchain.h +index ec80e2b..c702420 100644 +--- a/src/toolchain.h ++++ b/src/toolchain.h +@@ -32,10 +32,7 @@ class ToolChainInvocation { + + std::string command; + std::string lang; +- StrList command_args; +- StrList include_args; +- StrList lib_dir_args; +- StrList lib_args; +- StrList obj_args; ++ StrList inputs; ++ + ExecuteCommand executor; + }; +diff --git a/src/utils.cxx b/src/utils.cxx +index 6bb8c6c..e40b680 100644 +--- a/src/utils.cxx ++++ b/src/utils.cxx +@@ -4,6 +4,8 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include "utils.h" ++#include ++#include + #include + #include + #include +@@ -11,7 +13,10 @@ + #include + #include + #include ++#include ++#include + #include ++#include + #include + #include + #include +@@ -27,12 +32,17 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include "shlwapi.h" ++#include "PathCch.h" + + ////////////////////////////////////////////////////////// + // String helper methods adding cxx20 features to cxx14 // +@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { + return str.substr(substr.size(), str.size()); + } + ++/** ++ * Strips double quotes from front and back of string ++ * ++ * Returns str with no leading or trailing quotes ++ * ++ * Note: removes only one set of quotes ++ */ ++std::string stripquotes(const std::string& str) { ++ return strip(lstrip(str, "\""), "\""); ++} ++ + /** + * combines list of strings into one string joined on join_char + */ +@@ -337,7 +358,7 @@ std::string regexMatch( + if (!std::regex_match(searchDomain, match, reg, flag)) { + result_str = std::string(); + } else { +- result_str = match.str(); ++ result_str = match.str(1); + } + return result_str; + } +@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { + * null terminators. + * \param bsize the lengh of the padding to add + */ +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { ++char* pad_path(const char* pth, DWORD str_size, char padding_char, ++ DWORD bsize) { + // If str_size > bsize we get inappropriate conversion + // from signed to unsigned + if (str_size > bsize) { +@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { + padded_path[i] = pth[j]; + ++j; + } else { +- padded_path[i] = '|'; ++ padded_path[i] = padding_char; + } + } + padded_path[bsize] = '\0'; + return padded_path; + } + ++std::string escape_backslash(const std::string& path) { ++ std::string escaped; ++ escaped.reserve(path.length() * 2); ++ for (char const c : path) { ++ if (c == '\\') { ++ escaped += "\\\\"; ++ } else { ++ escaped += c; ++ } ++ } ++ return escaped; ++} ++ + /** + * Given a padded library path, return how much the path + * has been padded +@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { + return new_abs_out; + } + ++std::string MakePathAbsolute(const std::string& path) { ++ if (IsPathAbsolute(path)) { ++ return path; ++ } ++ // relative paths, assume they're relative to the CWD of the linker (as they have to be) ++ return join({GetCWD(), path}, "\\"); ++} ++ ++std::string CannonicalizePath(const std::string& path) { ++ std::wstring const wpath = ConvertASCIIToWide(path); ++ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; ++ const size_t buffer_size = ARRAYSIZE(canonicalized_path); ++ ++ HRESULT const status = PathCchCanonicalizeEx( ++ canonicalized_path, buffer_size, wpath.c_str(), ++ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support ++ ); ++ ++ if (!SUCCEEDED(status)) { ++ std::stringstream status_report; ++ status_report << "Cannot canonicalize path " + path + " error: " ++ << std::hex << status; ++ throw NameTooLongError(status_report.str().c_str()); ++ } ++ return ConvertWideToASCII(canonicalized_path); ++} ++ ++std::string EnsureValidLengthPath(const std::string& path) { ++ std::string proper_length_path = path; ++ if (path.length() > MAX_NAME_LEN) { ++ // Name is too long we need to attempt to shorten ++ std::string const short_path = short_name(path); ++ // If new, shortened path is too long, bail ++ proper_length_path = short_path; ++ } ++ return proper_length_path; ++} ++ + /** + * Mangles a string representing a path to have no path characters + * instead path characters (i.e. \\, :, etc) are replaced with +@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { + std::string mangle_name(const std::string& name) { + std::string abs_out; + std::string mangled_abs_out; +- if (IsPathAbsolute(name)) { +- abs_out = name; +- } else { +- // relative paths, assume they're relative to the CWD of the linker (as they have to be) +- abs_out = join({GetCWD(), name}, "\\"); +- } ++ abs_out = MakePathAbsolute(name); ++ abs_out = CannonicalizePath(abs_out); + // Now that we have the full path, check size +- if (abs_out.length() > MAX_NAME_LEN) { +- // Name is too long we need to attempt to shorten +- std::string const new_abs_out = short_name(abs_out); +- // If new, shortened path is too long, bail +- abs_out = new_abs_out; +- } ++ abs_out = EnsureValidLengthPath(abs_out); + char* chr_abs_out = new char[abs_out.length() + 1]; + strcpy(chr_abs_out, abs_out.c_str()); + replace_path_characters(chr_abs_out, abs_out.length()); +@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { + return false; + } + std::string const stripped_lib = strip_padding(lib); +- startswith(stripped_lib, prefix); ++ return startswith(stripped_lib, prefix); + } + + LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} +@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, + return std::string(); + } + ++PathRelocator::PathRelocator() { ++ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); ++ this->parseRelocate(); ++} ++ ++void PathRelocator::parseRelocate() { ++ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); ++ // relocations is a semi colon separated list of ++ // | separated pairs, of old_prefix|new_prefix ++ // where old prefix is either the stage or the ++ // old install root and new prefix is the dll location in the ++ // install tree or just the new install prefix ++ if (relocations.empty()) { ++ return; ++ } ++ const StrList mappings = split(relocations, ";"); ++ for (const auto& pair : mappings) { ++ const StrList old_new = split(pair, "|"); ++ const std::string& old = old_new[0]; ++ const std::string& new_ = old_new[1]; ++ this->old_new_map[old] = new_; ++ if (endswith(old, ".dll") || endswith(old, ".exe")) { ++ this->bc_ = false; ++ } ++ } ++} ++ ++std::string PathRelocator::getRelocation(std::string const& pe) { ++ if (this->bc_) { ++ return this->relocateBC(pe); ++ } ++ return this->relocateStage(pe); ++} ++ ++std::string PathRelocator::relocateBC(std::string const& pe) { ++ for (auto& root : this->old_new_map) { ++ if (startswith(pe, root.first)) { ++ std::array rel_root; ++ if (PathRelativePathToW( ++ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), ++ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), ++ FILE_ATTRIBUTE_NORMAL) != 0) { ++ // we have the pe's relative root in the old ++ // prefix, slap the new prefix on it and return ++ std::string const real_rel( ++ ConvertWideToASCII(std::wstring(&rel_root[0]))); ++ return join({root.second, real_rel}, "\\"); ++ } ++ } ++ } ++ return std::string(); ++} ++ ++std::string PathRelocator::relocateStage(std::string const& pe) { ++ try { ++ std::string prefix_loc = this->old_new_map.at(pe); ++ return prefix_loc; ++ } catch (std::out_of_range& e) { ++ return std::string(); ++ } ++} ++ + namespace { + std::vector system_locations = { + "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", + "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", + "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", + "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; +- + } + + bool LibraryFinder::IsSystem(const std::string& pth) { +@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { + return nullptr; + } + ++ScopedSid FileSecurity::GetCurrentUserSid() { ++ HANDLE token_handle = nullptr; ++ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, ++ &token_handle)) { ++ return nullptr; ++ } ++ std::unique_ptr const scoped_token( ++ token_handle, &::CloseHandle); ++ ++ DWORD buffer_size = 0; ++ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); ++ ++ std::vector buffer(buffer_size); ++ auto* token_user = reinterpret_cast(buffer.data()); ++ ++ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, ++ &buffer_size)) { ++ return nullptr; ++ } ++ ++ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); ++ void* sid_copy = std::malloc(sid_len); ++ if (sid_copy) { ++ ::CopySid(sid_len, sid_copy, token_user->User.Sid); ++ return ScopedSid(sid_copy); ++ } ++ return nullptr; ++} ++ ++bool FileSecurity::HasPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid) { ++ PACL dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, ++ DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ScopedLocalInfo const scoped_sd(sd_raw); ++ ++ TRUSTEE_W trustee = {nullptr}; ++ trustee.TrusteeForm = TRUSTEE_IS_SID; ++ trustee.TrusteeType = TRUSTEE_IS_USER; ++ trustee.ptstrName = static_cast(sid); ++ ++ ACCESS_MASK effective_rights = 0; ++ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) ++ return true; ++ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) ++ return true; ++ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) ++ return true; ++ ++ return (effective_rights & access_mask) == access_mask; ++} ++ ++bool FileSecurity::GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd) { ++ PACL old_dacl = nullptr; ++ PSECURITY_DESCRIPTOR sd_raw = nullptr; ++ ++ DWORD result = ::GetNamedSecurityInfoW( ++ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, ++ nullptr, &old_dacl, nullptr, &sd_raw); ++ ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ if (out_old_sd) ++ *out_old_sd = sd_raw; ++ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); ++ ++ EXPLICIT_ACCESS_W ea = {0}; ++ ea.grfAccessPermissions = access_mask; ++ ea.grfAccessMode = GRANT_ACCESS; ++ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ++ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; ++ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; ++ ea.Trustee.ptstrName = static_cast(sid); ++ ++ PACL new_dacl = nullptr; ++ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); ++ if (result != ERROR_SUCCESS) ++ return false; ++ ++ ScopedLocalInfo const scoped_new_dacl(new_dacl); ++ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, new_dacl, nullptr); ++ return (result == ERROR_SUCCESS); ++} ++ ++bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd) { ++ if (!sd) ++ return false; ++ BOOL present = FALSE; ++ BOOL defaulted = FALSE; ++ PACL dacl = nullptr; ++ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || ++ !present) ++ return false; ++ ++ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), ++ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, ++ nullptr, nullptr, dacl, ++ nullptr) == ERROR_SUCCESS; ++} ++ ++bool FileSecurity::GetAttributes(const std::wstring& file_path, ++ DWORD* out_attr) { ++ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); ++ if (attr == INVALID_FILE_ATTRIBUTES) ++ return false; ++ if (out_attr) ++ *out_attr = attr; ++ return true; ++} ++ ++bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { ++ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; ++} ++ ++ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) ++ : file_path_(std::move(file_path)), ++ desired_access_(desired_access), ++ original_sd_(nullptr), ++ current_user_sid_(nullptr), ++ acl_needs_revert_(false), ++ original_attributes_(0), ++ attributes_changed_(false) { ++ ++ // We must ensure we have permissions *first* before we try to ++ // change the file attributes in Phase 2. ++ ++ current_user_sid_ = FileSecurity::GetCurrentUserSid(); ++ if (!current_user_sid_) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), "Failed to get SID"); ++ } ++ ++ // Check if we need to modify ACLs ++ if (!FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get())) { ++ if (!FileSecurity::GrantPermission(file_path_, desired_access_, ++ current_user_sid_.get(), ++ &original_sd_)) { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to grant ACL"); ++ } ++ acl_needs_revert_ = true; ++ } ++ ++ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { ++ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { ++ // Remove the Read-Only bit ++ DWORD const new_attributes = ++ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; ++ ++ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { ++ attributes_changed_ = true; ++ } else { ++ // If we fail to remove Read-Only, we might still fail to write later. ++ // We throw here to be safe and consistent. ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to remove Read-Only attribute"); ++ } ++ } ++ } else { ++ throw std::system_error(static_cast(::GetLastError()), ++ std::system_category(), ++ "Failed to get file attributes"); ++ } ++} ++ ++ScopedFileAccess::~ScopedFileAccess() { ++ // We must restore attributes *before* we revert ACLs, because reverting ACLs ++ // might remove our permission to write attributes. ++ if (attributes_changed_) { ++ // We ignore errors in destructors to prevent termination ++ FileSecurity::SetAttributes(file_path_, original_attributes_); ++ } ++ ++ if (acl_needs_revert_ && original_sd_) { ++ FileSecurity::ApplyDescriptor(file_path_, original_sd_); ++ ::LocalFree(original_sd_); ++ } ++} ++ ++bool ScopedFileAccess::IsAccessGranted() const { ++ // If we had to change anything, we assume success (constructor would throw otherwise) ++ if (acl_needs_revert_ || attributes_changed_) ++ return true; ++ ++ return FileSecurity::HasPermission(file_path_, desired_access_, ++ current_user_sid_.get()); ++} ++ + NameTooLongError::NameTooLongError(char const* const message) + : std::runtime_error(message) {} + + char const* NameTooLongError::what() const { + return exception::what(); ++} ++ ++RCCompilerFailure::RCCompilerFailure(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* RCCompilerFailure::what() const { ++ return exception::what(); ++} ++ ++FileIOError::FileIOError(char const* const message) ++ : std::runtime_error(message) {} ++ ++char const* FileIOError::what() const { ++ return exception::what(); + } +\ No newline at end of file +diff --git a/src/utils.h b/src/utils.h +index b04d929..8ded476 100644 +--- a/src/utils.h ++++ b/src/utils.h +@@ -16,6 +16,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "version.h" + +@@ -52,7 +55,8 @@ enum ExitConditions { + LIB_REMOVE_FAILURE, + NORMALIZE_NAME_FAILURE, + COFF_PARSE_FAILURE, +- FILE_RENAME_FAILURE ++ FILE_RENAME_FAILURE, ++ CANNOT_OPEN_FILE_FAILURE + }; + + typedef std::vector StrList; +@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); + //Strips substr of LHS of the larger string + std::string lstrip(const std::string& s, const std::string& substr); + ++//Strips off leading and trailing quotes ++std::string stripquotes(const std::string& str); ++ + // Joins vector of strings by join character + std::string join(const StrList& args, const std::string& join_char = " "); + +@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); + + std::string mangle_name(const std::string& name); + ++std::string CannonicalizePath(const std::string& path); ++ + int get_padding_length(const std::string& name); + +-char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); ++char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', ++ DWORD bsize = MAX_NAME_LEN); ++ ++std::string escape_backslash(const std::string& path); + + void replace_path_characters(char* path, size_t len); + +@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); + + bool SpackInstalledLib(const std::string& lib); + ++std::string MakePathAbsolute(const std::string& path); ++ ++std::string EnsureValidLengthPath(const std::string& path); ++ + // File and File handle helpers // + /** + * @brief Returns boolean indicating whether +@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); + // System Helpers // + std::string reportLastError(); + ++struct LocalFreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ ::LocalFree(p); ++ } ++}; ++ ++// Custom deleter for Standard C pointers. ++struct FreeDeleter { ++ void operator()(void* p) const { ++ if (p) ++ std::free(p); ++ } ++}; ++ + // Data helpers // + + // Converts big endian data to little endian form +@@ -262,6 +293,71 @@ class LibraryFinder { + void EvalSearchPaths(); + }; + ++class PathRelocator { ++ private: ++ bool bc_; ++ std::string new_prefix_; ++ std::map old_new_map; ++ std::string relocateBC(std::string const& pe); ++ std::string relocateStage(std::string const& pe); ++ void parseRelocate(); ++ ++ public: ++ PathRelocator(); ++ std::string getRelocation(std::string const& pe); ++}; ++ ++using ScopedLocalInfo = std::unique_ptr; ++ ++using ScopedSid = std::unique_ptr; ++ ++class FileSecurity { ++ public: ++ FileSecurity() = delete; ++ ++ static ScopedSid GetCurrentUserSid(); ++ ++ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, ++ PSID sid); ++ ++ static bool GrantPermission(const std::wstring& file_path, ++ DWORD access_mask, PSID sid, ++ PSECURITY_DESCRIPTOR* out_old_sd); ++ ++ static bool ApplyDescriptor(const std::wstring& file_path, ++ PSECURITY_DESCRIPTOR sd); ++ ++ // Retrieves file attributes (e.g., ReadOnly, Hidden). ++ // Returns false if the file cannot be accessed. ++ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); ++ ++ // Sets file attributes. ++ // Returns false if the operation fails. ++ static bool SetAttributes(const std::wstring& file_path, DWORD attr); ++}; ++ ++class ScopedFileAccess { ++ public: ++ explicit ScopedFileAccess(std::wstring file_path, ++ DWORD desired_access = GENERIC_WRITE); ++ ~ScopedFileAccess(); ++ ++ bool IsAccessGranted() const; ++ ++ private: ++ std::wstring file_path_; ++ DWORD desired_access_; ++ ++ // ACL State ++ PSECURITY_DESCRIPTOR original_sd_; ++ ScopedSid current_user_sid_; ++ bool acl_needs_revert_; ++ ++ // Attribute State ++ DWORD original_attributes_; ++ bool attributes_changed_; ++}; ++ + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; + + const std::map path_to_special_characters{{'\\', '|'}, +@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { + virtual char const* what() const; + }; + ++class RCCompilerFailure : public std::runtime_error { ++ public: ++ RCCompilerFailure(char const* const message); ++ virtual char const* what() const; ++}; ++ ++class FileIOError : public std::runtime_error { ++ public: ++ FileIOError(char const* const message); ++ virtual char const* what() const; ++}; ++ + static bool DEBUG = false; +diff --git a/src/winrpath.cxx b/src/winrpath.cxx +index f98e063..8138983 100644 +--- a/src/winrpath.cxx ++++ b/src/winrpath.cxx +@@ -4,7 +4,6 @@ + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + */ + #include +-#include + #include // NOLINT + #include "winrpath.h" + #include +@@ -23,6 +22,7 @@ + #include + #include + #include ++#include + #include + + /* +@@ -37,21 +37,8 @@ + * \param name The dll name to check for sigils or special path characters + * + */ +-bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +- if (this->deploy) { +- return hasPathCharacters(dll_path); +- } +- // First check for the case we're relocating out of a buildcache +- bool reloc_spack = false; +- if (!(dll_path.find("") == std::string::npos) || +- !(dll_path.find("") == std::string::npos)) { +- reloc_spack = true; +- } +- // If not, maybe we're just relocating a binary on the same system +- if (!reloc_spack) { +- reloc_spack = hasPathCharacters(dll_path); +- } +- return reloc_spack; ++bool LibRename::SpackCheckForDll(const std::string& dll_path) { ++ return hasPathCharacters(dll_path); + } + + /* +@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { + * the dll name found at `name_loc` to the absolute path of + * + */ +-bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { +- if (this->deploy) { +- int const padding_len = get_padding_length(dll_path); +- if (padding_len < MIN_PADDING_THRESHOLD) { +- // path is too long to mark as a Spack path +- // use shorter sigil +- char short_sigil[] = ""; +- // use _snprintf as it does not null terminate and we're writing into the middle +- // of a null terminated string we want to later read from properly +- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); +- } else { +- char long_sigil[] = ""; +- // See _snprintf comment above for use context +- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); +- } +- } else { +- if (SpackInstalledLib(dll_path)) { +- return true; +- } +- std::string const file_name = basename(dll_path); +- if (file_name.empty()) { +- std::cerr << "Unable to extract filename from dll for relocation" +- << "\n"; +- return false; +- } +- LibraryFinder lib_finder; +- std::string new_library_loc = +- lib_finder.FindLibrary(file_name, dll_path); +- if (new_library_loc.empty()) { +- std::cerr << "Unable to find library " << file_name << " from " +- << dll_path << " for relocation" << "\n"; +- return false; +- } +- if (new_library_loc.length() > MAX_NAME_LEN) { +- try { +- new_library_loc = short_name(new_library_loc); +- } catch (NameTooLongError& e) { +- return false; +- } +- } +- char* new_lib_pth = +- pad_path(new_library_loc.c_str(), +- static_cast(new_library_loc.size())); +- if (!new_lib_pth) { +- return false; +- } +- replace_special_characters(new_lib_pth, MAX_NAME_LEN); +- +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); ++bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { ++ if (SpackInstalledLib(dll_path)) { ++ return true; + } ++ PathRelocator relocator; ++ std::string const new_loc = relocator.getRelocation(dll_path); ++ if (new_loc.empty()) { ++ std::cerr << "Cannot find relocation mapping for library " << dll_path ++ << "\n"; ++ return false; ++ } ++ char* new_lib_pth = ++ pad_path(new_loc.c_str(), static_cast(new_loc.size())); ++ if (!new_lib_pth) { ++ return false; ++ } ++ replace_special_characters(new_lib_pth, MAX_NAME_LEN); ++ ++ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about ++ // size differences w.r.t the path to the new library ++ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + return true; + } + +@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + import_table_offset + + (import_image_descriptor->Name - rva_import_directory); + std::string const str_dll_name = std::string(imported_dll); +- if (this->SpackCheckForDll(str_dll_name)) { +- if (!this->RenameDll(imported_dll, str_dll_name)) { ++ if (LibRename::SpackCheckForDll(str_dll_name)) { ++ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { + std::cerr << "Unable to relocate DLL reference: " + << str_dll_name << "\n"; + return false; +@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { + * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names + * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout + */ +-LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) +- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} ++LibRename::LibRename(std::string p_exe, bool full, bool replace) ++ : replace(replace), full(full), pe(std::move(p_exe)) { ++ this->pe = MakePathAbsolute(this->pe); ++} + + LibRename::LibRename(std::string p_exe, std::string coff, bool full, +- bool deploy, bool replace) ++ bool replace) + : replace(replace), + full(full), + pe(std::move(p_exe)), +- deploy(deploy), + coff(std::move(coff)) { ++ this->pe = MakePathAbsolute(this->pe); + std::string const coff_path = stem(this->coff); + this->tmp_def_file = coff_path + "-tmp.def"; + this->def_file = coff_path + ".def"; +@@ -322,7 +280,9 @@ bool LibRename::ComputeDefFile() { + npos) { // Skip header in export block if still present + break; + } +- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; ++ output_file << " " ++ << regexMatch(line, R"(^\s*(?:[0-9A-Fa-f]+\s+)*(\S+))") ++ << '\n'; + } + input_file.close(); + output_file.close(); +@@ -357,7 +317,7 @@ bool LibRename::ExecuteRename() { + // exes + // We do not bother with defs for things that don't have + // import libraries +- if (!this->deploy && !this->coff.empty()) { ++ if (!this->coff.empty()) { + // Extract DLL + if (!this->ComputeDefFile()) { + debug("Failed to compute def file"); +@@ -439,15 +399,23 @@ bool LibRename::ExecutePERename() { + std::cerr << e.what() << "\n"; + return false; + } +- HANDLE pe_handle = CreateFileW( +- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, +- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { +- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() +- << ": " << reportLastError() << "\n"; ++ try { ++ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); ++ HANDLE pe_handle = CreateFileW( ++ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, ++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { ++ std::cerr << "Unable to acquire file handle to " ++ << ConvertWideToASCII(pe_path) << ": " ++ << reportLastError() << "\n"; ++ return false; ++ } ++ return LibRename::FindDllAndRename(pe_handle); ++ } catch (const std::system_error& e) { ++ std::cerr << "Could not obtain write access: " << e.what() ++ << " (Error Code: " << e.code().value() << ")" << '\n'; + return false; + } +- return this->FindDllAndRename(pe_handle); + } + + /* Construct the line needed to produce a new import library +diff --git a/src/winrpath.h b/src/winrpath.h +index e166465..16aeff6 100644 +--- a/src/winrpath.h ++++ b/src/winrpath.h +@@ -31,9 +31,8 @@ + + class LibRename { + public: +- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, +- bool replace); +- LibRename(std::string p_exe, bool full, bool deploy, bool replace); ++ LibRename(std::string p_exe, std::string coff, bool full, bool replace); ++ LibRename(std::string p_exe, bool full, bool replace); + bool ExecuteRename(); + bool ExecuteLibRename(); + bool ExecutePERename(); +@@ -42,9 +41,9 @@ class LibRename { + std::string ComputeDefLine(); + + private: +- bool FindDllAndRename(HANDLE& pe_in); +- bool SpackCheckForDll(const std::string& dll_path) const; +- bool RenameDll(char* name_loc, const std::string& dll_path) const; ++ static bool FindDllAndRename(HANDLE& pe_in); ++ static bool SpackCheckForDll(const std::string& dll_path) ; ++ static bool RenameDll(char* name_loc, const std::string& dll_path) ; + ExecuteCommand def_executor; + ExecuteCommand lib_executor; + std::string pe; +@@ -53,6 +52,5 @@ class LibRename { + std::string def_file; + std::string tmp_def_file; + bool full; +- bool deploy; + bool replace; + }; +diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat +index 88285fd..2ae5e83 100644 +--- a/test/setup_and_drive_test.bat ++++ b/test/setup_and_drive_test.bat +@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST + SET SPACK_SHORT_SPEC=test%msvc + SET SPACK_SYSTEM_DIRS=%PATH% + SET SPACK_MANAGED_DIRS=%CD%\tmp +-SET SPACK_RELOCATE_PATH=%CD%\tmp + + nmake test +\ No newline at end of file From eca3131a7b2d1545be0973b84f7adb1a506b0d0e Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 9 Mar 2026 16:56:22 -0400 Subject: [PATCH 21/81] wip Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 3c8cf4bb8ba..31f428146db 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): version("develop", branch="main") with when("@develop platform=windows"): - patch("rc_updates_28.patch") + patch("rc_updates_33.patch") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From 3cd03217cea95e4ce02e42ca6c926add0bc63e20 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 9 Mar 2026 16:56:36 -0400 Subject: [PATCH 22/81] wip Signed-off-by: John Parent --- .../compiler_wrapper/rc_updates_13.patch | 1452 ------------ .../compiler_wrapper/rc_updates_14.patch | 1893 --------------- .../compiler_wrapper/rc_updates_15.patch | 1942 ---------------- .../compiler_wrapper/rc_updates_16.patch | 1948 ---------------- .../compiler_wrapper/rc_updates_17.patch | 1991 ---------------- .../compiler_wrapper/rc_updates_18.patch | 1986 ---------------- .../compiler_wrapper/rc_updates_19.patch | 1987 ---------------- .../compiler_wrapper/rc_updates_20.patch | 1988 ---------------- .../compiler_wrapper/rc_updates_21.patch | 2034 ---------------- .../compiler_wrapper/rc_updates_22.patch | 2047 ---------------- .../compiler_wrapper/rc_updates_23.patch | 2042 ---------------- .../compiler_wrapper/rc_updates_24.patch | 2055 ----------------- .../compiler_wrapper/rc_updates_25.patch | 2055 ----------------- ...c_updates_28.patch => rc_updates_31.patch} | 158 +- ...c_updates_27.patch => rc_updates_32.patch} | 179 +- ...c_updates_26.patch => rc_updates_33.patch} | 202 +- 16 files changed, 375 insertions(+), 25584 deletions(-) delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch rename repos/spack_repo/builtin/packages/compiler_wrapper/{rc_updates_28.patch => rc_updates_31.patch} (93%) rename repos/spack_repo/builtin/packages/compiler_wrapper/{rc_updates_27.patch => rc_updates_32.patch} (93%) rename repos/spack_repo/builtin/packages/compiler_wrapper/{rc_updates_26.patch => rc_updates_33.patch} (93%) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch deleted file mode 100644 index 2d67e0ef3a0..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_13.patch +++ /dev/null @@ -1,1452 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..abae347 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,9 +115,10 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -@@ -124,9 +130,10 @@ test_relocate_exe: build_and_check_test_sample - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -141,31 +148,44 @@ test_relocate_dll: build_and_check_test_sample - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..9d6b3dd 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,8 @@ - #include "ld.h" - #include - #include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +21,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->input_files.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -52,18 +75,16 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib - this->rpath_executor = - ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ - {def, piped_args, "-name:" + pe_name, - "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -+ {link_run.get_rsp_files()}, -+ this->input_files, - this->lib_dir_args, - })); - this->rpath_executor.Execute(); -@@ -73,10 +94,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +123,45 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = -+ EnsureValidLengthPath(MakePathAbsolute(pe_stage_name)); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char const* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..2b4b1b0 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::ofstream rsp_out("spack-build.rsp"); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"spack-build.rsp"}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..0008731 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,30 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..87be08e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - this->include_args.insert(this->include_args.begin(), inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->input_files.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); -@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // Collect include args as we need to ensure Spack - // Includes come first -+ -+ // Collect all CLI args -+ // certain args have implications when defined in certain orders on -+ // the command line -+ // so we need supply those to the linker exactly as they were -+ // we also need to know where we can inject spack libraries and the resource -+ // where they come first but also do not impact binary naming -+ // Command args will impact naming if they contain a def file or /out -+ // regardless of ordering, otherwise naming is determined by -+ // input file order, preserve that - for (char const* const* co = cli; *co; co++) { - std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - this->include_args.push_back(arg); - this->include_args.emplace_back(*(++co)); - } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -+ } else if ((endswith(norm_arg, ".lib") && -+ (norm_arg.find("implib:") == std::string::npos)) || -+ (endswith(norm_arg, ".obj")) || -+ (endswith(norm_arg, ".res")) || -+ (startswith(norm_arg, "@"))) { -+ // Capture all lib, obj, resource, and rsp files -+ // in the order they're specified since all have binary -+ // naming implications and we don't need finer granularity -+ this->input_files.push_back(arg); -+ } else { - this->command_args.push_back(arg); - } - } -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..5360ead 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -35,7 +35,6 @@ class ToolChainInvocation { - StrList command_args; - StrList include_args; - StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList input_files; - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..f870d11 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -12,6 +12,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -28,11 +29,13 @@ - #include - #include - #include -+#include - #include - #include - #include - #include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +184,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +543,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,7 +558,7 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; -@@ -623,6 +638,46 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, // Output buffer -+ buffer_size, // Size of output buffer in characters -+ wpath.c_str(), // Input path string -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +688,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +734,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -841,9 +887,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ -+ // Ensure token handle is closed when this scope exits -+ std::unique_ptr scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ // Allocate buffer for TOKEN_USER -+ std::vector buffer(buffer_size); -+ PTOKEN_USER token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (!sid_copy) { -+ return nullptr; -+ } -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ -+ return ScopedSid(sid_copy); -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) { -+ return false; -+ } -+ -+ // Wrap raw pointer to ensure cleanup -+ ScopedLocalInfo scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {0}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) { -+ return false; -+ } -+ -+ // Map GENERIC mappings to specific bits because GetEffectiveRightsFromAcl -+ // returns specific bits (e.g., FILE_WRITE_DATA) not GENERIC_WRITE. -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) { -+ return true; -+ } -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) { -+ return true; -+ } -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) { -+ return true; -+ } -+ -+ // Fallback for exact bit matches -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ // 1. Get Current DACL (Snapshot) -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) { -+ return false; -+ } -+ -+ // If caller requested the old SD, transfer ownership. -+ // Otherwise, wrap it locally to free it at end of scope. -+ if (out_old_sd) { -+ *out_old_sd = sd_raw; -+ } -+ -+ // Helper to manage sd_raw if we are NOT returning it to the caller -+ ScopedLocalInfo temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ // Merges the new rule with the existing DACL -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ -+ if (result != ERROR_SUCCESS) { -+ return false; -+ } -+ -+ ScopedLocalInfo scoped_new_dacl(new_dacl); -+ -+ // Apply the new ACL to the file -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted)) { -+ return false; -+ } -+ -+ if (!present) -+ return false; // No DACL to restore -+ -+ DWORD result = ::SetNamedSecurityInfoW( -+ const_cast(file_path.c_str()), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, nullptr, dacl, nullptr); -+ -+ return (result == ERROR_SUCCESS); -+} -+ -+ScopedFileAccess::ScopedFileAccess(const std::wstring& file_path, -+ DWORD desired_access) -+ : file_path_(file_path), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ needs_revert_(false) { -+ -+ // 1. Get Current User SID -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to retrieve current user SID"); -+ } -+ -+ // 2. Check existing permissions -+ // If we already have what we need, we simply return (no revert needed). -+ if (FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ return; -+ } -+ -+ // 3. Attempt to Grant Permissions -+ // If this fails, we throw immediately. The destructor will NOT run for a -+ // partially constructed object, but since we use smart pointers (ScopedSid), -+ // no memory leaks will occur. -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant requested permissions"); -+ } -+ -+ // If we got here, we successfully changed the ACL. -+ needs_revert_ = true; -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ if (needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If the constructor finished and we are in 'needs_revert_' state, -+ // we definitely have access. -+ if (needs_revert_) -+ return true; -+ -+ // Otherwise, we perform a live check. -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..35bbe98 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,12 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +206,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +232,23 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) { -+ ::LocalFree(p); -+ } -+ } -+}; -+ -+// Custom deleter for Standard C pointers (malloc/free) used for SIDs. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) { -+ std::free(p); -+ } -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,59 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+using ScopedLocalInfo = std::unique_ptr; -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ // Disallow instantiation -+ FileSecurity() = delete; -+ FileSecurity(const FileSecurity&) = delete; -+ FileSecurity& operator=(const FileSecurity&) = delete; -+ -+ // Gets the SID for the current process user. -+ // Returns a unique_ptr to the SID structure. -+ static ScopedSid GetCurrentUserSid(); -+ -+ // Checks if the specified SID has the requested access rights. -+ // access_mask: The specific rights to check (e.g., GENERIC_WRITE). -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ // Grants the specified permissions to the SID. -+ // If successful, returns true and populates out_old_sd with the original -+ // security descriptor (if provided) so it can be reverted later. -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ // Reverts permissions by applying a saved Security Descriptor. -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+}; -+ -+class ScopedFileAccess { -+ public: -+ // Constructor attempts to grant permission immediately. -+ // Throws std::system_error if permissions cannot be obtained. -+ explicit ScopedFileAccess(const std::wstring& file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ -+ ~ScopedFileAccess(); -+ -+ // Returns true if access is currently valid. -+ // (Always true immediately after construction, but useful if external -+ // factors might revoke access). -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool needs_revert_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +358,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..73b20fa 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,7 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include -+#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +23,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -439,15 +440,22 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -+ << ": " << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch deleted file mode 100644 index bb3ee2f469a..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_14.patch +++ /dev/null @@ -1,1893 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..9229bb5 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->input_files.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib - this->rpath_executor = - ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ - {def, piped_args, "-name:" + pe_name, - "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -+ {link_run.get_rsp_files()}, -+ this->input_files, - this->lib_dir_args, - })); - this->rpath_executor.Execute(); -@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..2b4b1b0 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::ofstream rsp_out("spack-build.rsp"); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"spack-build.rsp"}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..0008731 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,30 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..87be08e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - this->include_args.insert(this->include_args.begin(), inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->input_files.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); -@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // Collect include args as we need to ensure Spack - // Includes come first -+ -+ // Collect all CLI args -+ // certain args have implications when defined in certain orders on -+ // the command line -+ // so we need supply those to the linker exactly as they were -+ // we also need to know where we can inject spack libraries and the resource -+ // where they come first but also do not impact binary naming -+ // Command args will impact naming if they contain a def file or /out -+ // regardless of ordering, otherwise naming is determined by -+ // input file order, preserve that - for (char const* const* co = cli; *co; co++) { - std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - this->include_args.push_back(arg); - this->include_args.emplace_back(*(++co)); - } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -+ } else if ((endswith(norm_arg, ".lib") && -+ (norm_arg.find("implib:") == std::string::npos)) || -+ (endswith(norm_arg, ".obj")) || -+ (endswith(norm_arg, ".res")) || -+ (startswith(norm_arg, "@"))) { -+ // Capture all lib, obj, resource, and rsp files -+ // in the order they're specified since all have binary -+ // naming implications and we don't need finer granularity -+ this->input_files.push_back(arg); -+ } else { - this->command_args.push_back(arg); - } - } -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..5360ead 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -35,7 +35,6 @@ class ToolChainInvocation { - StrList command_args; - StrList include_args; - StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList input_files; - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..d7dad08 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // --- PHASE 1: ACL HANDLING --- -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ // --- PHASE 2: ATTRIBUTE HANDLING --- -+ // Now that we (presumably) have write access from Phase 1, -+ // we check for the Read-Only bit. -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // --- REVERT PHASE 1: Restore Attributes --- -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ // --- REVERT PHASE 2: Restore ACLs --- -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..29416db 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,79 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // --- NEW: Attribute Helpers --- -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ // 1. Grants ACL permissions. -+ // 2. Removes Read-Only attribute if present. -+ // Throws std::system_error on failure. -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ -+ // 1. Restores original attributes (re-enables Read-Only if needed). -+ // 2. Reverts ACL permissions. -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +378,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch deleted file mode 100644 index 02e729d879a..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_15.patch +++ /dev/null @@ -1,1942 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..dda65fa 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,13 +14,28 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - this->pe_stream_.open(this->file_, - std::ios::in | std::ios::out | std::ios::binary); - return this->pe_stream_.is_open(); -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..9229bb5 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->input_files.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib - this->rpath_executor = - ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ - {def, piped_args, "-name:" + pe_name, - "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -+ {link_run.get_rsp_files()}, -+ this->input_files, - this->lib_dir_args, - })); - this->rpath_executor.Execute(); -@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..2b4b1b0 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::ofstream rsp_out("spack-build.rsp"); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"spack-build.rsp"}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..0008731 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,30 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..87be08e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - this->include_args.insert(this->include_args.begin(), inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->input_files.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); -@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // Collect include args as we need to ensure Spack - // Includes come first -+ -+ // Collect all CLI args -+ // certain args have implications when defined in certain orders on -+ // the command line -+ // so we need supply those to the linker exactly as they were -+ // we also need to know where we can inject spack libraries and the resource -+ // where they come first but also do not impact binary naming -+ // Command args will impact naming if they contain a def file or /out -+ // regardless of ordering, otherwise naming is determined by -+ // input file order, preserve that - for (char const* const* co = cli; *co; co++) { - std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - this->include_args.push_back(arg); - this->include_args.emplace_back(*(++co)); - } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -+ } else if ((endswith(norm_arg, ".lib") && -+ (norm_arg.find("implib:") == std::string::npos)) || -+ (endswith(norm_arg, ".obj")) || -+ (endswith(norm_arg, ".res")) || -+ (startswith(norm_arg, "@"))) { -+ // Capture all lib, obj, resource, and rsp files -+ // in the order they're specified since all have binary -+ // naming implications and we don't need finer granularity -+ this->input_files.push_back(arg); -+ } else { - this->command_args.push_back(arg); - } - } -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..5360ead 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -35,7 +35,6 @@ class ToolChainInvocation { - StrList command_args; - StrList include_args; - StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList input_files; - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..d7dad08 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // --- PHASE 1: ACL HANDLING --- -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ // --- PHASE 2: ATTRIBUTE HANDLING --- -+ // Now that we (presumably) have write access from Phase 1, -+ // we check for the Read-Only bit. -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // --- REVERT PHASE 1: Restore Attributes --- -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ // --- REVERT PHASE 2: Restore ACLs --- -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch deleted file mode 100644 index b1fabddc904..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_16.patch +++ /dev/null @@ -1,1948 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..9229bb5 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,19 +22,40 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->input_files.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -+ - // We're creating a PE, we need to create an appropriate import lib - std::string const imp_lib_name = link_run.get_implib_name(); - // If there is no implib, we don't need to bother -@@ -52,18 +76,16 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib - this->rpath_executor = - ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ - {def, piped_args, "-name:" + pe_name, - "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -+ {link_run.get_rsp_files()}, -+ this->input_files, - this->lib_dir_args, - })); - this->rpath_executor.Execute(); -@@ -73,10 +95,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +124,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..2b4b1b0 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,70 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::ofstream rsp_out("spack-build.rsp"); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"spack-build.rsp"}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +195,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +226,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +251,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +262,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +282,26 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..0008731 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,30 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..87be08e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -26,7 +26,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - this->include_args.insert(this->include_args.begin(), inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->input_files.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -37,8 +37,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - - DWORD ToolChainInvocation::InvokeToolchain() { - StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -+ {this->command_args, this->include_args, this->lib_dir_args, -+ this->input_files})); - this->executor = ExecuteCommand(this->command, command_line); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); -@@ -55,6 +55,16 @@ DWORD ToolChainInvocation::InvokeToolchain() { - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - // Collect include args as we need to ensure Spack - // Includes come first -+ -+ // Collect all CLI args -+ // certain args have implications when defined in certain orders on -+ // the command line -+ // so we need supply those to the linker exactly as they were -+ // we also need to know where we can inject spack libraries and the resource -+ // where they come first but also do not impact binary naming -+ // Command args will impact naming if they contain a def file or /out -+ // regardless of ordering, otherwise naming is determined by -+ // input file order, preserve that - for (char const* const* co = cli; *co; co++) { - std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -@@ -71,18 +81,16 @@ void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { - this->include_args.push_back(arg); - this->include_args.emplace_back(*(++co)); - } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -+ } else if ((endswith(norm_arg, ".lib") && -+ (norm_arg.find("implib:") == std::string::npos)) || -+ (endswith(norm_arg, ".obj")) || -+ (endswith(norm_arg, ".res")) || -+ (startswith(norm_arg, "@"))) { -+ // Capture all lib, obj, resource, and rsp files -+ // in the order they're specified since all have binary -+ // naming implications and we don't need finer granularity -+ this->input_files.push_back(arg); -+ } else { - this->command_args.push_back(arg); - } - } -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..5360ead 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -35,7 +35,6 @@ class ToolChainInvocation { - StrList command_args; - StrList include_args; - StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList input_files; - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..d7dad08 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,237 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // --- PHASE 1: ACL HANDLING --- -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ // --- PHASE 2: ATTRIBUTE HANDLING --- -+ // Now that we (presumably) have write access from Phase 1, -+ // we check for the Read-Only bit. -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // --- REVERT PHASE 1: Restore Attributes --- -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ // --- REVERT PHASE 2: Restore ACLs --- -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch deleted file mode 100644 index 23d443870dc..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_17.patch +++ /dev/null @@ -1,1991 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..5a86f82 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -- if (!fileExists(imp_lib_name)) { -+ // trying to rename or if the imp lib already existed -+ if (!fileExists(imp_lib_name) && created_imp_lib) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not -@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..6f6d0bd 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +197,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +228,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +253,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +264,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +284,26 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..8dbbfa9 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..b6b7f75 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -9,6 +9,8 @@ - #include "spack_env.h" - #include "utils.h" - -+using StrRefList = std::vector; -+ - /** - * @brief - */ -@@ -32,10 +34,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch deleted file mode 100644 index b0a9ee9312f..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_18.patch +++ /dev/null @@ -1,1986 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..5a86f82 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -- if (!fileExists(imp_lib_name)) { -+ // trying to rename or if the imp lib already existed -+ if (!fileExists(imp_lib_name) && created_imp_lib) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not -@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..e13e97d 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,30 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +82,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +120,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +197,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +228,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +253,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +264,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +284,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..8dbbfa9 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch deleted file mode 100644 index 022e312403b..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_19.patch +++ /dev/null @@ -1,1987 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..5a86f82 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -- if (!fileExists(imp_lib_name)) { -+ // trying to rename or if the imp lib already existed -+ if (!fileExists(imp_lib_name) && created_imp_lib) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not -@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..8dbbfa9 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,7 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +50,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +66,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch deleted file mode 100644 index 325d8d53fcd..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_20.patch +++ /dev/null @@ -1,1988 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..dbcb708 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..5a86f82 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,24 +22,56 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -- if (!fileExists(imp_lib_name)) { -+ // trying to rename or if the imp lib already existed -+ if (!fileExists(imp_lib_name) && created_imp_lib) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not -@@ -52,20 +87,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +103,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +132,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch deleted file mode 100644 index ad905becb8f..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_21.patch +++ /dev/null @@ -1,2034 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..a9a3c6b 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,14 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..0143e96 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,7 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName(); - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..4f69ddc 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,30 +22,75 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -+ // trying to rename or if the imp lib already existed - if (!fileExists(imp_lib_name)) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not - // the concern of this wrapper -- return 0; -+ if (created_imp_lib) { -+ return 0; -+ } -+ // Check if the imp lib is associated with the link command -+ // we just ran -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ std::string const long_name = existing_coff.GetLongName(); -+ std::string const link_name = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(link_run.get_out()))); -+ if (long_name != link_name) { -+ return 0; -+ } - } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +100,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +116,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +145,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch deleted file mode 100644 index 84efa4403b7..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_22.patch +++ /dev/null @@ -1,2047 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+} -+ -+std::string CoffParser::GetShortName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..3452b01 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,30 +22,73 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. if it does before we run the toolchain, -+ // another command created it, this command probably -+ // isn't supposed to overwrite it -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ // but if it exists already, likely would not be overwritten -+ // If the file does not exist, this run could be responsible for it -+ bool const created_imp_lib = !fileExists(imp_lib_name); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ - // If there is no implib, we don't need to bother -- // trying to rename -- if (!fileExists(imp_lib_name)) { -+ // trying to rename or if the imp lib already existed -+ if (!fileExists(imp_lib_name) || !created_imp_lib) { - // There are numerous contexts in which a PE file - // may not export symbols, some are bugs in the - // upstream project, most are valid, all are not - // the concern of this wrapper - return 0; - } -+ // Check if the imp lib is associated with the link command -+ // we just ran -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ std::string const short_name = existing_coff.GetShortName(); -+ std::string const link_name = link_run.get_out(); -+ debug("internal lib name: " + short_name + " Pe name: " + link_name); -+ if (short_name != link_name) { -+ return 0; -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +98,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +114,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +143,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch deleted file mode 100644 index 13d4fad3dd3..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_23.patch +++ /dev/null @@ -1,2042 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+} -+ -+std::string CoffParser::GetShortName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..3ee24c6 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,18 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ std::string const short_name = existing_coff.GetShortName(); -+ std::string const link_name = link_run.get_out(); -+ debug("internal lib name: " + short_name + " Pe name: " + link_name); -+ if (short_name != link_name) { -+ return 0; -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +97,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +113,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +142,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch deleted file mode 100644 index adcf9a901b1..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_24.patch +++ /dev/null @@ -1,2055 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+} -+ -+std::string CoffParser::GetShortName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..4be86ce 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran, if we cannot process the coff file -+ // we should exit -+ { -+ // Create temp scope to ensure all handles are appropriately deallocated -+ // since the Coff readers use RAII -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ if (!existing_coff.Parse()) { -+ std::cerr << "Unable to parse coff file: " << imp_lib_name -+ << " unable to determine import library provenance\n"; -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ std::string const short_name = existing_coff.GetShortName(); -+ std::string const link_name = link_run.get_out(); -+ -+ if (short_name != link_name) { -+ debug("internal lib name: " + short_name + -+ " Pe name: " + link_name + " are not equivalent"); -+ return 0; -+ } -+ existing_coff_reader.Close(); -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch deleted file mode 100644 index 030b2b987b1..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_25.patch +++ /dev/null @@ -1,2055 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+} -+ -+std::string CoffParser::GetShortName() { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..2f4bc79 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran, if we cannot process the coff file -+ // we should exit -+ { -+ // Create temp scope to ensure all handles are appropriately deallocated -+ // since the Coff readers use RAII -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ if (!existing_coff.Parse()) { -+ std::cerr << "Unable to parse coff file: " << imp_lib_name -+ << " unable to determine import library provenance\n"; -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ std::string const short_name = existing_coff.GetShortName(); -+ std::string const link_name = basename(link_run.get_out()); -+ -+ if (short_name != link_name) { -+ debug("internal lib name: " + short_name + -+ " Pe name: " + link_name + " are not equivalent"); -+ return 0; -+ } -+ existing_coff_reader.Close(); -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { - } - } - -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; -+} -+ - /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,6 +4,8 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include - #include -@@ -11,7 +13,10 @@ - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +32,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..32da29c 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -357,7 +315,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +397,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return this->FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch similarity index 93% rename from repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch rename to repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch index 2f9805f63fb..05be7dc3b4b 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_28.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch @@ -253,7 +253,7 @@ index 54646df..e5111b4 100644 clean : clean-test clean-cl diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 +index 25f4b28..4cee1f0 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) @@ -268,19 +268,23 @@ index 25f4b28..4c1fb4d 100644 return false; } int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { +@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { std::cout << "DLL: " << data << "\n"; } -+std::string CoffParser::GetLongName() { ++std::string CoffParser::GetLongName() const { ++ // TODO(johnwparent): I think we can access the ++ // 2nd index of the members vec to get the long ++ // name + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { + return std::string(mem.member->data); + } + } ++ return std::string(); +} + -+std::string CoffParser::GetShortName() { ++std::string CoffParser::GetShortName() const { + for (auto mem : this->coff_.members) { + if (mem.header->Name[0] != '/') { + int i = 0; @@ -293,20 +297,29 @@ index 25f4b28..4c1fb4d 100644 + } + return std::string(); +} ++ ++std::string CoffParser::GetName() const { ++ std::string maybe_name = this->GetLongName(); ++ if (maybe_name.empty()) { ++ maybe_name = this->GetShortName(); ++ } ++ return maybe_name; ++} + void CoffParser::Report() { for (auto mem : this->coff_.members) { if (mem.member->is_longname) { diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 +index 6cf7728..e485af0 100644 --- a/src/coff_parser.h +++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { +@@ -43,6 +43,9 @@ class CoffParser { bool NormalizeName(std::string& name); void Report(); int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); ++ std::string GetLongName() const; ++ std::string GetShortName() const; ++ std::string GetName() const; static int Validate(std::string& coff); }; @@ -458,7 +471,7 @@ index b00bf1d..b4f8376 100644 } diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..0131acb 100644 +index 5cc0683..a9f45ae 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -6,6 +6,9 @@ @@ -529,7 +542,7 @@ index 5cc0683..0131acb 100644 // If there is no implib, we don't need to bother // trying to rename if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { // the concern of this wrapper return 0; } @@ -547,11 +560,15 @@ index 5cc0683..0131acb 100644 + << " unable to determine import library provenance\n"; + return ExitConditions::COFF_PARSE_FAILURE; + } -+ std::string const short_name = existing_coff.GetShortName(); ++ std::string const shorter_name = existing_coff.GetName(); + std::string const link_name = basename(link_run.get_out()); ++ if (shorter_name.empty() || link_name.empty()) { ++ debug("Cannot determine either PE or COFF names (Pe: " + link_name + ++ "; Coff: " + shorter_name + ") skipping absolute rename\n"); ++ } + -+ if (short_name != link_name) { -+ debug("internal lib name: " + short_name + ++ if (shorter_name != link_name) { ++ debug("internal lib name: " + shorter_name + + " Pe name: " + link_name + " are not equivalent"); + return 0; + } @@ -561,7 +578,7 @@ index 5cc0683..0131acb 100644 std::string pe_name; try { pe_name = link_run.get_mangled_out(); -@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { return ExitConditions::NORMALIZE_NAME_FAILURE; } std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; @@ -589,7 +606,7 @@ index 5cc0683..0131acb 100644 this->rpath_executor.Execute(); DWORD const err_code = this->rpath_executor.Join(); if (err_code != 0) { -@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { } CoffReaderWriter coff_reader(abs_out_imp_lib_name); CoffParser coff(&coff_reader); @@ -603,7 +620,7 @@ index 5cc0683..0131acb 100644 if (!coff.NormalizeName(pe_name)) { debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); -@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { } return ret_code; } @@ -672,7 +689,7 @@ index a9f3a82..191aaba 100644 + static std::string createRC(LinkerInvocation& link_run); }; diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 +index 4ed82be..7340044 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -3,14 +3,19 @@ @@ -734,7 +751,7 @@ index 4ed82be..328c489 100644 } else if (startswith(normal_token, "def")) { this->def_file_ = strip(split(*token, ":", 1)[1], "\""); } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { +@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { this->piped_args_.at(normal_token).emplace_back(*token); } } @@ -786,10 +803,11 @@ index 4ed82be..328c489 100644 } if (this->implibname_.empty()) { std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + this->implibname_ = name + ".lib"; } - } - ++ this->makeRsp(); ++} ++ +void LinkerInvocation::processInputFiles() { + StrList new_input_files; + for (auto input = this->input_files_.begin(); @@ -854,12 +872,10 @@ index 4ed82be..328c489 100644 + return true; + } + return false; -+} -+ + } + /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { +@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { */ void LinkerInvocation::processDefFile() { @@ -875,7 +891,7 @@ index 4ed82be..328c489 100644 } std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { +@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { if (keyword == "NAME") { this->is_exe_ = true; def_line >> this->pe_name_; @@ -900,7 +916,7 @@ index 4ed82be..328c489 100644 const std::string def_name = stem(this->def_file_); const std::string def_path = this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { +@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { if (!def_out) { std::cerr << "Error: could not open output def file: " << rename_def << "\n"; @@ -908,7 +924,7 @@ index 4ed82be..328c489 100644 } for (const auto& line : exports) { def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { +@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { def_in.close(); } @@ -922,7 +938,7 @@ index 4ed82be..328c489 100644 std::string lib_link_line; for (const auto& var_args : this->piped_args_) { // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { +@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { return lib_link_line; } @@ -1154,10 +1170,10 @@ index ec80e2b..c702420 100644 ExecuteCommand executor; }; diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..e40b680 100644 +index 6bb8c6c..4f83780 100644 --- a/src/utils.cxx +++ b/src/utils.cxx -@@ -4,6 +4,8 @@ +@@ -4,14 +4,20 @@ * SPDX-License-Identifier: (Apache-2.0 OR MIT) */ #include "utils.h" @@ -1165,8 +1181,9 @@ index 6bb8c6c..e40b680 100644 +#include #include #include ++#include #include -@@ -11,7 +13,10 @@ + #include #include #include #include @@ -1177,7 +1194,7 @@ index 6bb8c6c..e40b680 100644 #include #include #include -@@ -27,12 +32,17 @@ +@@ -27,12 +33,17 @@ #include #include #include @@ -1195,7 +1212,7 @@ index 6bb8c6c..e40b680 100644 ////////////////////////////////////////////////////////// // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { +@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { return str.substr(substr.size(), str.size()); } @@ -1213,7 +1230,7 @@ index 6bb8c6c..e40b680 100644 /** * combines list of strings into one string joined on join_char */ -@@ -337,7 +358,7 @@ std::string regexMatch( +@@ -337,7 +359,7 @@ std::string regexMatch( if (!std::regex_match(searchDomain, match, reg, flag)) { result_str = std::string(); } else { @@ -1222,7 +1239,7 @@ index 6bb8c6c..e40b680 100644 } return result_str; } -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { +@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { * null terminators. * \param bsize the lengh of the padding to add */ @@ -1232,7 +1249,7 @@ index 6bb8c6c..e40b680 100644 // If str_size > bsize we get inappropriate conversion // from signed to unsigned if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { +@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { padded_path[i] = pth[j]; ++j; } else { @@ -1260,7 +1277,57 @@ index 6bb8c6c..e40b680 100644 /** * Given a padded library path, return how much the path * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { +@@ -591,11 +627,38 @@ std::string getSFN(const std::string& path) { + // Use "disable string parsing" prefix in case + // the path is too long + std::string const escaped = R"(\\?\)" + path; ++ // We cannot get the sfn for a path that doesn't exist ++ // if we find that the sfn we're looking for doesn't exist ++ // create a stub of the file, and allow the subsequent ++ // commands to overwrite it ++ if (!PathFileExistsA(path.c_str())) { ++ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, ++ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (h_file == INVALID_HANDLE_VALUE) { ++ debug("File " + path + ++ " does not exist, nor can it be created, unable to " ++ "compute SFN\n"); ++ CloseHandle(h_file); ++ return std::string(); ++ } ++ CloseHandle(h_file); ++ } + // Get SFN length so we can create buffer + DWORD const sfn_size = + GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT + char* sfn = new char[sfn_size + 1]; +- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ if (!res) { ++ ++ std::cerr << "Failed to process short name for " << path ++ << " Error: " << reportLastError() << "\n"; ++ } ++ if (!sfn && res) { ++ // buffer was too small ++ debug("buffer too small; had: " + std::to_string(sfn_size) + ++ " needed: " + std::to_string(res)); ++ } ++ printf("SFN: %s\n", sfn); + // sfn is null terminated per win32 api + // Ensure we strip out the disable string parsing prefix + std::string s_sfn = lstrip(sfn, R"(\\?\)"); +@@ -611,7 +674,9 @@ std::string getSFN(const std::string& path) { + */ + std::string short_name(const std::string& path) { + // Get SFN for path to name ++ debug("path: " + path); + std::string const new_abs_out = getSFN(path); ++ debug("New abs out: " + new_abs_out); + if (new_abs_out.length() > MAX_NAME_LEN) { + std::cerr << "DLL path " << path << " too long to relocate.\n"; + std::cerr << "Shortened DLL path " << new_abs_out +@@ -623,6 +688,45 @@ std::string short_name(const std::string& path) { return new_abs_out; } @@ -1296,6 +1363,7 @@ index 6bb8c6c..e40b680 100644 + if (path.length() > MAX_NAME_LEN) { + // Name is too long we need to attempt to shorten + std::string const short_path = short_name(path); ++ debug("short path" + short_path); + // If new, shortened path is too long, bail + proper_length_path = short_path; + } @@ -1305,7 +1373,7 @@ index 6bb8c6c..e40b680 100644 /** * Mangles a string representing a path to have no path characters * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { +@@ -633,19 +737,10 @@ std::string short_name(const std::string& path) { std::string mangle_name(const std::string& name) { std::string abs_out; std::string mangled_abs_out; @@ -1328,7 +1396,7 @@ index 6bb8c6c..e40b680 100644 char* chr_abs_out = new char[abs_out.length() + 1]; strcpy(chr_abs_out, abs_out.c_str()); replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { +@@ -688,7 +783,7 @@ bool SpackInstalledLib(const std::string& lib) { return false; } std::string const stripped_lib = strip_padding(lib); @@ -1337,7 +1405,7 @@ index 6bb8c6c..e40b680 100644 } LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, +@@ -786,13 +881,74 @@ std::string LibraryFinder::Finder(const std::string& pth, return std::string(); } @@ -1413,7 +1481,7 @@ index 6bb8c6c..e40b680 100644 } bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { +@@ -841,9 +997,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { return nullptr; } @@ -1818,7 +1886,7 @@ index b04d929..8ded476 100644 + static bool DEBUG = false; diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..8138983 100644 +index f98e063..76566eb 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -4,7 +4,6 @@ @@ -1980,7 +2048,7 @@ index f98e063..8138983 100644 } - output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; + output_file << " " -+ << regexMatch(line, R"(^\s*(?:[0-9A-Fa-f]+\s+)*(\S+))") ++ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") + << '\n'; } input_file.close(); diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch similarity index 93% rename from repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch rename to repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch index ab84d16ac73..9a0795d03dc 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_27.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch @@ -253,7 +253,7 @@ index 54646df..e5111b4 100644 clean : clean-test clean-cl diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 +index 25f4b28..4cee1f0 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) @@ -268,19 +268,23 @@ index 25f4b28..4c1fb4d 100644 return false; } int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { +@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { std::cout << "DLL: " << data << "\n"; } -+std::string CoffParser::GetLongName() { ++std::string CoffParser::GetLongName() const { ++ // TODO(johnwparent): I think we can access the ++ // 2nd index of the members vec to get the long ++ // name + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { + return std::string(mem.member->data); + } + } ++ return std::string(); +} + -+std::string CoffParser::GetShortName() { ++std::string CoffParser::GetShortName() const { + for (auto mem : this->coff_.members) { + if (mem.header->Name[0] != '/') { + int i = 0; @@ -293,20 +297,29 @@ index 25f4b28..4c1fb4d 100644 + } + return std::string(); +} ++ ++std::string CoffParser::GetName() const { ++ std::string maybe_name = this->GetLongName(); ++ if (maybe_name.empty()) { ++ maybe_name = this->GetShortName(); ++ } ++ return maybe_name; ++} + void CoffParser::Report() { for (auto mem : this->coff_.members) { if (mem.member->is_longname) { diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 +index 6cf7728..e485af0 100644 --- a/src/coff_parser.h +++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { +@@ -43,6 +43,9 @@ class CoffParser { bool NormalizeName(std::string& name); void Report(); int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); ++ std::string GetLongName() const; ++ std::string GetShortName() const; ++ std::string GetName() const; static int Validate(std::string& coff); }; @@ -458,7 +471,7 @@ index b00bf1d..b4f8376 100644 } diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..2f4bc79 100644 +index 5cc0683..a9f45ae 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -6,6 +6,9 @@ @@ -490,9 +503,6 @@ index 5cc0683..2f4bc79 100644 + return ExitConditions::TOOLCHAIN_FAILURE; + } + -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ + // Add produced RC file to linker CLI to inject ID + // This needs to be at the end of either libs or objs or rsp files + // so long as this RC file is not the first binary @@ -506,6 +516,9 @@ index 5cc0683..2f4bc79 100644 if (ret_code != 0) { return ret_code; } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); + // Next we want to construct the proper commmand line to // recreate the import library from the same set of obj files @@ -529,14 +542,14 @@ index 5cc0683..2f4bc79 100644 // If there is no implib, we don't need to bother // trying to rename if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { // the concern of this wrapper return 0; } + // There is an imp lib, so + // Check if the imp lib is associated with the link command + // we just ran, if we cannot process the coff file -+ // we should exit ++ // we should exit with failure since something is unexpected + { + // Create temp scope to ensure all handles are appropriately deallocated + // since the Coff readers use RAII @@ -547,11 +560,15 @@ index 5cc0683..2f4bc79 100644 + << " unable to determine import library provenance\n"; + return ExitConditions::COFF_PARSE_FAILURE; + } -+ std::string const short_name = existing_coff.GetShortName(); ++ std::string const shorter_name = existing_coff.GetName(); + std::string const link_name = basename(link_run.get_out()); ++ if (shorter_name.empty() || link_name.empty()) { ++ debug("Cannot determine either PE or COFF names (Pe: " + link_name + ++ "; Coff: " + shorter_name + ") skipping absolute rename\n"); ++ } + -+ if (short_name != link_name) { -+ debug("internal lib name: " + short_name + ++ if (shorter_name != link_name) { ++ debug("internal lib name: " + shorter_name + + " Pe name: " + link_name + " are not equivalent"); + return 0; + } @@ -561,7 +578,7 @@ index 5cc0683..2f4bc79 100644 std::string pe_name; try { pe_name = link_run.get_mangled_out(); -@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { return ExitConditions::NORMALIZE_NAME_FAILURE; } std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; @@ -589,7 +606,7 @@ index 5cc0683..2f4bc79 100644 this->rpath_executor.Execute(); DWORD const err_code = this->rpath_executor.Join(); if (err_code != 0) { -@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { } CoffReaderWriter coff_reader(abs_out_imp_lib_name); CoffParser coff(&coff_reader); @@ -603,7 +620,7 @@ index 5cc0683..2f4bc79 100644 if (!coff.NormalizeName(pe_name)) { debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); -@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { } return ret_code; } @@ -672,7 +689,7 @@ index a9f3a82..191aaba 100644 + static std::string createRC(LinkerInvocation& link_run); }; diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 +index 4ed82be..1933591 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -3,14 +3,19 @@ @@ -734,7 +751,7 @@ index 4ed82be..328c489 100644 } else if (startswith(normal_token, "def")) { this->def_file_ = strip(split(*token, ":", 1)[1], "\""); } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { +@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { this->piped_args_.at(normal_token).emplace_back(*token); } } @@ -786,10 +803,11 @@ index 4ed82be..328c489 100644 } if (this->implibname_.empty()) { std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + this->implibname_ = name + ".lib"; } - } - ++ this->makeRsp(); ++} ++ +void LinkerInvocation::processInputFiles() { + StrList new_input_files; + for (auto input = this->input_files_.begin(); @@ -846,20 +864,18 @@ index 4ed82be..328c489 100644 + throw FileIOError("Unable to open lib rsp file"); + } + for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; ++ rsp_out << escape_backslash(line) << "\n"; + } + rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; ++ this->input_files_ = {"@" + rsp_name}; ++ this->rsp_files_ = {"@" + rsp_name}; + return true; + } + return false; -+} -+ + } + /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { +@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { */ void LinkerInvocation::processDefFile() { @@ -875,7 +891,7 @@ index 4ed82be..328c489 100644 } std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { +@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { if (keyword == "NAME") { this->is_exe_ = true; def_line >> this->pe_name_; @@ -900,7 +916,7 @@ index 4ed82be..328c489 100644 const std::string def_name = stem(this->def_file_); const std::string def_path = this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { +@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { if (!def_out) { std::cerr << "Error: could not open output def file: " << rename_def << "\n"; @@ -908,7 +924,7 @@ index 4ed82be..328c489 100644 } for (const auto& line : exports) { def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { +@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { def_in.close(); } @@ -922,7 +938,7 @@ index 4ed82be..328c489 100644 std::string lib_link_line; for (const auto& var_args : this->piped_args_) { // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { +@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { return lib_link_line; } @@ -1154,10 +1170,10 @@ index ec80e2b..c702420 100644 ExecuteCommand executor; }; diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..e40b680 100644 +index 6bb8c6c..4f83780 100644 --- a/src/utils.cxx +++ b/src/utils.cxx -@@ -4,6 +4,8 @@ +@@ -4,14 +4,20 @@ * SPDX-License-Identifier: (Apache-2.0 OR MIT) */ #include "utils.h" @@ -1165,8 +1181,9 @@ index 6bb8c6c..e40b680 100644 +#include #include #include ++#include #include -@@ -11,7 +13,10 @@ + #include #include #include #include @@ -1177,7 +1194,7 @@ index 6bb8c6c..e40b680 100644 #include #include #include -@@ -27,12 +32,17 @@ +@@ -27,12 +33,17 @@ #include #include #include @@ -1195,7 +1212,7 @@ index 6bb8c6c..e40b680 100644 ////////////////////////////////////////////////////////// // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { +@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { return str.substr(substr.size(), str.size()); } @@ -1213,7 +1230,7 @@ index 6bb8c6c..e40b680 100644 /** * combines list of strings into one string joined on join_char */ -@@ -337,7 +358,7 @@ std::string regexMatch( +@@ -337,7 +359,7 @@ std::string regexMatch( if (!std::regex_match(searchDomain, match, reg, flag)) { result_str = std::string(); } else { @@ -1222,7 +1239,7 @@ index 6bb8c6c..e40b680 100644 } return result_str; } -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { +@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { * null terminators. * \param bsize the lengh of the padding to add */ @@ -1232,7 +1249,7 @@ index 6bb8c6c..e40b680 100644 // If str_size > bsize we get inappropriate conversion // from signed to unsigned if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { +@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { padded_path[i] = pth[j]; ++j; } else { @@ -1260,7 +1277,57 @@ index 6bb8c6c..e40b680 100644 /** * Given a padded library path, return how much the path * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { +@@ -591,11 +627,38 @@ std::string getSFN(const std::string& path) { + // Use "disable string parsing" prefix in case + // the path is too long + std::string const escaped = R"(\\?\)" + path; ++ // We cannot get the sfn for a path that doesn't exist ++ // if we find that the sfn we're looking for doesn't exist ++ // create a stub of the file, and allow the subsequent ++ // commands to overwrite it ++ if (!PathFileExistsA(path.c_str())) { ++ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, ++ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (h_file == INVALID_HANDLE_VALUE) { ++ debug("File " + path + ++ " does not exist, nor can it be created, unable to " ++ "compute SFN\n"); ++ CloseHandle(h_file); ++ return std::string(); ++ } ++ CloseHandle(h_file); ++ } + // Get SFN length so we can create buffer + DWORD const sfn_size = + GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT + char* sfn = new char[sfn_size + 1]; +- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ if (!res) { ++ ++ std::cerr << "Failed to process short name for " << path ++ << " Error: " << reportLastError() << "\n"; ++ } ++ if (!sfn && res) { ++ // buffer was too small ++ debug("buffer too small; had: " + std::to_string(sfn_size) + ++ " needed: " + std::to_string(res)); ++ } ++ printf("SFN: %s\n", sfn); + // sfn is null terminated per win32 api + // Ensure we strip out the disable string parsing prefix + std::string s_sfn = lstrip(sfn, R"(\\?\)"); +@@ -611,7 +674,9 @@ std::string getSFN(const std::string& path) { + */ + std::string short_name(const std::string& path) { + // Get SFN for path to name ++ debug("path: " + path); + std::string const new_abs_out = getSFN(path); ++ debug("New abs out: " + new_abs_out); + if (new_abs_out.length() > MAX_NAME_LEN) { + std::cerr << "DLL path " << path << " too long to relocate.\n"; + std::cerr << "Shortened DLL path " << new_abs_out +@@ -623,6 +688,45 @@ std::string short_name(const std::string& path) { return new_abs_out; } @@ -1296,6 +1363,7 @@ index 6bb8c6c..e40b680 100644 + if (path.length() > MAX_NAME_LEN) { + // Name is too long we need to attempt to shorten + std::string const short_path = short_name(path); ++ debug("short path" + short_path); + // If new, shortened path is too long, bail + proper_length_path = short_path; + } @@ -1305,7 +1373,7 @@ index 6bb8c6c..e40b680 100644 /** * Mangles a string representing a path to have no path characters * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { +@@ -633,19 +737,10 @@ std::string short_name(const std::string& path) { std::string mangle_name(const std::string& name) { std::string abs_out; std::string mangled_abs_out; @@ -1328,7 +1396,7 @@ index 6bb8c6c..e40b680 100644 char* chr_abs_out = new char[abs_out.length() + 1]; strcpy(chr_abs_out, abs_out.c_str()); replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { +@@ -688,7 +783,7 @@ bool SpackInstalledLib(const std::string& lib) { return false; } std::string const stripped_lib = strip_padding(lib); @@ -1337,7 +1405,7 @@ index 6bb8c6c..e40b680 100644 } LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, +@@ -786,13 +881,74 @@ std::string LibraryFinder::Finder(const std::string& pth, return std::string(); } @@ -1413,7 +1481,7 @@ index 6bb8c6c..e40b680 100644 } bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { +@@ -841,9 +997,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { return nullptr; } @@ -1818,7 +1886,7 @@ index b04d929..8ded476 100644 + static bool DEBUG = false; diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..8466442 100644 +index f98e063..76566eb 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -4,7 +4,6 @@ @@ -1974,17 +2042,18 @@ index f98e063..8466442 100644 std::string const coff_path = stem(this->coff); this->tmp_def_file = coff_path + "-tmp.def"; this->def_file = coff_path + ".def"; -@@ -322,7 +280,8 @@ bool LibRename::ComputeDefFile() { +@@ -322,7 +280,9 @@ bool LibRename::ComputeDefFile() { npos) { // Skip header in export block if still present break; } - output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; -+ output_file << " " << regexMatch(line, R"(^\s+(?:\d+\s+)?(\w+))") ++ output_file << " " ++ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") + << '\n'; } input_file.close(); output_file.close(); -@@ -357,7 +316,7 @@ bool LibRename::ExecuteRename() { +@@ -357,7 +317,7 @@ bool LibRename::ExecuteRename() { // exes // We do not bother with defs for things that don't have // import libraries @@ -1993,7 +2062,7 @@ index f98e063..8466442 100644 // Extract DLL if (!this->ComputeDefFile()) { debug("Failed to compute def file"); -@@ -439,15 +398,23 @@ bool LibRename::ExecutePERename() { +@@ -439,15 +399,23 @@ bool LibRename::ExecutePERename() { std::cerr << e.what() << "\n"; return false; } diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch similarity index 93% rename from repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch rename to repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch index 528b53a721e..76af37d69bf 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_26.patch +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch @@ -253,7 +253,7 @@ index 54646df..e5111b4 100644 clean : clean-test clean-cl diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4c1fb4d 100644 +index 25f4b28..4cee1f0 100644 --- a/src/coff_parser.cxx +++ b/src/coff_parser.cxx @@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) @@ -268,19 +268,23 @@ index 25f4b28..4c1fb4d 100644 return false; } int const invalid_valid_sig = -@@ -578,6 +579,28 @@ void CoffParser::ReportLongName(const char* data) { +@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { std::cout << "DLL: " << data << "\n"; } -+std::string CoffParser::GetLongName() { ++std::string CoffParser::GetLongName() const { ++ // TODO(johnwparent): I think we can access the ++ // 2nd index of the members vec to get the long ++ // name + for (auto mem : this->coff_.members) { + if (mem.member->is_longname) { + return std::string(mem.member->data); + } + } ++ return std::string(); +} + -+std::string CoffParser::GetShortName() { ++std::string CoffParser::GetShortName() const { + for (auto mem : this->coff_.members) { + if (mem.header->Name[0] != '/') { + int i = 0; @@ -293,20 +297,29 @@ index 25f4b28..4c1fb4d 100644 + } + return std::string(); +} ++ ++std::string CoffParser::GetName() const { ++ std::string maybe_name = this->GetLongName(); ++ if (maybe_name.empty()) { ++ maybe_name = this->GetShortName(); ++ } ++ return maybe_name; ++} + void CoffParser::Report() { for (auto mem : this->coff_.members) { if (mem.member->is_longname) { diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..4efe78e 100644 +index 6cf7728..e485af0 100644 --- a/src/coff_parser.h +++ b/src/coff_parser.h -@@ -43,6 +43,8 @@ class CoffParser { +@@ -43,6 +43,9 @@ class CoffParser { bool NormalizeName(std::string& name); void Report(); int Verify(); -+ std::string GetLongName(); -+ std::string GetShortName(); ++ std::string GetLongName() const; ++ std::string GetShortName() const; ++ std::string GetName() const; static int Validate(std::string& coff); }; @@ -458,7 +471,7 @@ index b00bf1d..b4f8376 100644 } diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..2f4bc79 100644 +index 5cc0683..a9f45ae 100644 --- a/src/ld.cxx +++ b/src/ld.cxx @@ -6,6 +6,9 @@ @@ -490,9 +503,6 @@ index 5cc0683..2f4bc79 100644 + return ExitConditions::TOOLCHAIN_FAILURE; + } + -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ + // Add produced RC file to linker CLI to inject ID + // This needs to be at the end of either libs or objs or rsp files + // so long as this RC file is not the first binary @@ -506,6 +516,9 @@ index 5cc0683..2f4bc79 100644 if (ret_code != 0) { return ret_code; } ++ ++ // We're creating a PE, we need to create an appropriate import lib ++ std::string const imp_lib_name = link_run.get_implib_name(); + // Next we want to construct the proper commmand line to // recreate the import library from the same set of obj files @@ -529,14 +542,14 @@ index 5cc0683..2f4bc79 100644 // If there is no implib, we don't need to bother // trying to rename if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,31 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { // the concern of this wrapper return 0; } + // There is an imp lib, so + // Check if the imp lib is associated with the link command + // we just ran, if we cannot process the coff file -+ // we should exit ++ // we should exit with failure since something is unexpected + { + // Create temp scope to ensure all handles are appropriately deallocated + // since the Coff readers use RAII @@ -547,11 +560,15 @@ index 5cc0683..2f4bc79 100644 + << " unable to determine import library provenance\n"; + return ExitConditions::COFF_PARSE_FAILURE; + } -+ std::string const short_name = existing_coff.GetShortName(); ++ std::string const shorter_name = existing_coff.GetName(); + std::string const link_name = basename(link_run.get_out()); ++ if (shorter_name.empty() || link_name.empty()) { ++ debug("Cannot determine either PE or COFF names (Pe: " + link_name + ++ "; Coff: " + shorter_name + ") skipping absolute rename\n"); ++ } + -+ if (short_name != link_name) { -+ debug("internal lib name: " + short_name + ++ if (shorter_name != link_name) { ++ debug("internal lib name: " + shorter_name + + " Pe name: " + link_name + " are not equivalent"); + return 0; + } @@ -561,7 +578,7 @@ index 5cc0683..2f4bc79 100644 std::string pe_name; try { pe_name = link_run.get_mangled_out(); -@@ -52,20 +110,15 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { return ExitConditions::NORMALIZE_NAME_FAILURE; } std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; @@ -589,7 +606,7 @@ index 5cc0683..2f4bc79 100644 this->rpath_executor.Execute(); DWORD const err_code = this->rpath_executor.Join(); if (err_code != 0) { -@@ -73,10 +126,13 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { } CoffReaderWriter coff_reader(abs_out_imp_lib_name); CoffParser coff(&coff_reader); @@ -603,7 +620,7 @@ index 5cc0683..2f4bc79 100644 if (!coff.NormalizeName(pe_name)) { debug("Failed to normalize name for COFF file: " + abs_out_imp_lib_name); -@@ -99,3 +155,48 @@ DWORD LdInvocation::InvokeToolchain() { +@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { } return ret_code; } @@ -672,7 +689,7 @@ index a9f3a82..191aaba 100644 + static std::string createRC(LinkerInvocation& link_run); }; diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..328c489 100644 +index 4ed82be..1933591 100644 --- a/src/linker_invocation.cxx +++ b/src/linker_invocation.cxx @@ -3,14 +3,19 @@ @@ -734,7 +751,7 @@ index 4ed82be..328c489 100644 } else if (startswith(normal_token, "def")) { this->def_file_ = strip(split(*token, ":", 1)[1], "\""); } else if (this->piped_args_.find(normal_token) != -@@ -68,24 +83,37 @@ void LinkerInvocation::Parse() { +@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { this->piped_args_.at(normal_token).emplace_back(*token); } } @@ -786,10 +803,11 @@ index 4ed82be..328c489 100644 } if (this->implibname_.empty()) { std::string const name = strip(this->output_, ext); -@@ -93,6 +121,72 @@ void LinkerInvocation::Parse() { + this->implibname_ = name + ".lib"; } - } - ++ this->makeRsp(); ++} ++ +void LinkerInvocation::processInputFiles() { + StrList new_input_files; + for (auto input = this->input_files_.begin(); @@ -846,20 +864,18 @@ index 4ed82be..328c489 100644 + throw FileIOError("Unable to open lib rsp file"); + } + for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; ++ rsp_out << escape_backslash(line) << "\n"; + } + rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; ++ this->input_files_ = {"@" + rsp_name}; ++ this->rsp_files_ = {"@" + rsp_name}; + return true; + } + return false; -+} -+ + } + /** - * processDefFile reads a def file passed to the linker - * looking for either LIBRARY or NAME keywords -@@ -104,11 +198,15 @@ void LinkerInvocation::Parse() { +@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { */ void LinkerInvocation::processDefFile() { @@ -875,7 +891,7 @@ index 4ed82be..328c489 100644 } std::string line; -@@ -131,18 +229,22 @@ void LinkerInvocation::processDefFile() { +@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { if (keyword == "NAME") { this->is_exe_ = true; def_line >> this->pe_name_; @@ -900,7 +916,7 @@ index 4ed82be..328c489 100644 const std::string def_name = stem(this->def_file_); const std::string def_path = this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +254,7 @@ void LinkerInvocation::processDefFile() { +@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { if (!def_out) { std::cerr << "Error: could not open output def file: " << rename_def << "\n"; @@ -908,7 +924,7 @@ index 4ed82be..328c489 100644 } for (const auto& line : exports) { def_out << line << "\n"; -@@ -162,11 +265,11 @@ void LinkerInvocation::processDefFile() { +@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { def_in.close(); } @@ -922,7 +938,7 @@ index 4ed82be..328c489 100644 std::string lib_link_line; for (const auto& var_args : this->piped_args_) { // Most of these should be single arguments -@@ -182,22 +285,30 @@ std::string LinkerInvocation::get_lib_link_args() { +@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { return lib_link_line; } @@ -1154,10 +1170,10 @@ index ec80e2b..c702420 100644 ExecuteCommand executor; }; diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..71941bf 100644 +index 6bb8c6c..8596808 100644 --- a/src/utils.cxx +++ b/src/utils.cxx -@@ -4,6 +4,8 @@ +@@ -4,14 +4,20 @@ * SPDX-License-Identifier: (Apache-2.0 OR MIT) */ #include "utils.h" @@ -1165,8 +1181,9 @@ index 6bb8c6c..71941bf 100644 +#include #include #include ++#include #include -@@ -11,7 +13,10 @@ + #include #include #include #include @@ -1177,7 +1194,7 @@ index 6bb8c6c..71941bf 100644 #include #include #include -@@ -27,12 +32,17 @@ +@@ -27,12 +33,17 @@ #include #include #include @@ -1195,7 +1212,7 @@ index 6bb8c6c..71941bf 100644 ////////////////////////////////////////////////////////// // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +191,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { +@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { return str.substr(substr.size(), str.size()); } @@ -1213,7 +1230,16 @@ index 6bb8c6c..71941bf 100644 /** * combines list of strings into one string joined on join_char */ -@@ -529,7 +550,8 @@ void replace_path_characters(char* path, size_t len) { +@@ -337,7 +359,7 @@ std::string regexMatch( + if (!std::regex_match(searchDomain, match, reg, flag)) { + result_str = std::string(); + } else { +- result_str = match.str(); ++ result_str = match.str(1); + } + return result_str; + } +@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { * null terminators. * \param bsize the lengh of the padding to add */ @@ -1223,7 +1249,7 @@ index 6bb8c6c..71941bf 100644 // If str_size > bsize we get inappropriate conversion // from signed to unsigned if (str_size > bsize) { -@@ -543,13 +565,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { +@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { padded_path[i] = pth[j]; ++j; } else { @@ -1251,7 +1277,46 @@ index 6bb8c6c..71941bf 100644 /** * Given a padded library path, return how much the path * has been padded -@@ -623,6 +658,44 @@ std::string short_name(const std::string& path) { +@@ -591,11 +627,37 @@ std::string getSFN(const std::string& path) { + // Use "disable string parsing" prefix in case + // the path is too long + std::string const escaped = R"(\\?\)" + path; ++ // We cannot get the sfn for a path that doesn't exist ++ // if we find that the sfn we're looking for doesn't exist ++ // create a stub of the file, and allow the subsequent ++ // commands to overwrite it ++ if (!PathFileExistsA(path.c_str())) { ++ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, ++ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); ++ if (h_file == INVALID_HANDLE_VALUE) { ++ debug("File " + path + ++ " does not exist, nor can it be created, unable to " ++ "compute SFN\n"); ++ CloseHandle(h_file); ++ return std::string(); ++ } ++ CloseHandle(h_file); ++ } + // Get SFN length so we can create buffer + DWORD const sfn_size = + GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT + char* sfn = new char[sfn_size + 1]; +- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); ++ if (!res) { ++ ++ std::cerr << "Failed to process short name for " << path ++ << " Error: " << reportLastError() << "\n"; ++ } ++ if (!sfn && res) { ++ // buffer was too small ++ debug("buffer too small; had: " + std::to_string(sfn_size) + ++ " needed: " + std::to_string(res)); ++ } + // sfn is null terminated per win32 api + // Ensure we strip out the disable string parsing prefix + std::string s_sfn = lstrip(sfn, R"(\\?\)"); +@@ -623,6 +685,44 @@ std::string short_name(const std::string& path) { return new_abs_out; } @@ -1296,7 +1361,7 @@ index 6bb8c6c..71941bf 100644 /** * Mangles a string representing a path to have no path characters * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +706,10 @@ std::string short_name(const std::string& path) { +@@ -633,19 +733,10 @@ std::string short_name(const std::string& path) { std::string mangle_name(const std::string& name) { std::string abs_out; std::string mangled_abs_out; @@ -1319,7 +1384,7 @@ index 6bb8c6c..71941bf 100644 char* chr_abs_out = new char[abs_out.length() + 1]; strcpy(chr_abs_out, abs_out.c_str()); replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +752,7 @@ bool SpackInstalledLib(const std::string& lib) { +@@ -688,7 +779,7 @@ bool SpackInstalledLib(const std::string& lib) { return false; } std::string const stripped_lib = strip_padding(lib); @@ -1328,7 +1393,7 @@ index 6bb8c6c..71941bf 100644 } LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +850,74 @@ std::string LibraryFinder::Finder(const std::string& pth, +@@ -786,13 +877,74 @@ std::string LibraryFinder::Finder(const std::string& pth, return std::string(); } @@ -1404,7 +1469,7 @@ index 6bb8c6c..71941bf 100644 } bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +966,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { +@@ -841,9 +993,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { return nullptr; } @@ -1809,7 +1874,7 @@ index b04d929..8ded476 100644 + static bool DEBUG = false; diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..8466442 100644 +index f98e063..24198a9 100644 --- a/src/winrpath.cxx +++ b/src/winrpath.cxx @@ -4,7 +4,6 @@ @@ -1852,7 +1917,7 @@ index f98e063..8466442 100644 } /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { +@@ -66,58 +53,36 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { * the dll name found at `name_loc` to the absolute path of * */ @@ -1903,26 +1968,34 @@ index f98e063..8466442 100644 - return false; - } - replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); +bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { + if (SpackInstalledLib(dll_path)) { + return true; - } ++ } + PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); ++ std::string new_loc = relocator.getRelocation(dll_path); + if (new_loc.empty()) { + std::cerr << "Cannot find relocation mapping for library " << dll_path + << "\n"; + return false; + } ++ try { ++ new_loc = ++ EnsureValidLengthPath(CannonicalizePath(MakePathAbsolute(new_loc))); ++ } catch (NameTooLongError& e) { ++ std::cerr << "Cannot relocate path " << new_loc ++ << "it is too long to be relocated safely.\n"; ++ return false; ++ } + +- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about +- // size differences w.r.t the path to the new library +- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); + char* new_lib_pth = + pad_path(new_loc.c_str(), static_cast(new_loc.size())); + if (!new_lib_pth) { + return false; -+ } + } + replace_special_characters(new_lib_pth, MAX_NAME_LEN); + + // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about @@ -1931,7 +2004,7 @@ index f98e063..8466442 100644 return true; } -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { +@@ -206,8 +171,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { import_table_offset + (import_image_descriptor->Name - rva_import_directory); std::string const str_dll_name = std::string(imported_dll); @@ -1942,7 +2015,7 @@ index f98e063..8466442 100644 std::cerr << "Unable to relocate DLL reference: " << str_dll_name << "\n"; return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { +@@ -244,16 +209,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout */ @@ -1965,17 +2038,18 @@ index f98e063..8466442 100644 std::string const coff_path = stem(this->coff); this->tmp_def_file = coff_path + "-tmp.def"; this->def_file = coff_path + ".def"; -@@ -322,7 +280,8 @@ bool LibRename::ComputeDefFile() { +@@ -322,7 +289,9 @@ bool LibRename::ComputeDefFile() { npos) { // Skip header in export block if still present break; } - output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; -+ output_file << " " << regexMatch(line, R"(^\s+(?:\d+\s+)?(\w+))") ++ output_file << " " ++ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") + << '\n'; } input_file.close(); output_file.close(); -@@ -357,7 +316,7 @@ bool LibRename::ExecuteRename() { +@@ -357,7 +326,7 @@ bool LibRename::ExecuteRename() { // exes // We do not bother with defs for things that don't have // import libraries @@ -1984,7 +2058,7 @@ index f98e063..8466442 100644 // Extract DLL if (!this->ComputeDefFile()) { debug("Failed to compute def file"); -@@ -439,15 +398,23 @@ bool LibRename::ExecutePERename() { +@@ -439,15 +408,23 @@ bool LibRename::ExecutePERename() { std::cerr << e.what() << "\n"; return false; } From 09c2d0bbde2c75ba8e08ef23ab9c71e8317b4b71 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 4 May 2026 16:37:44 -0400 Subject: [PATCH 23/81] cleanup package Signed-off-by: John Parent --- .../packages/compiler_wrapper/package.py | 4 +- .../compiler_wrapper/rc_updates_31.patch | 2143 ----------------- .../compiler_wrapper/rc_updates_32.patch | 2143 ----------------- .../compiler_wrapper/rc_updates_33.patch | 2139 ---------------- 4 files changed, 1 insertion(+), 6428 deletions(-) delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch delete mode 100644 repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 31f428146db..7445b5ab15b 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -49,6 +49,7 @@ class CompilerWrapper(Package, NMakePackage): maintainers("haampie") license("Apache-2.0 OR MIT") + default_builder = "nmake" if IS_WINDOWS else "generic" build_system("generic", conditional("nmake", when="platform=windows"), default=default_builder) @@ -58,9 +59,6 @@ class CompilerWrapper(Package, NMakePackage): else: version("develop", branch="main") - with when("@develop platform=windows"): - patch("rc_updates_33.patch") - def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get # their way to the default view diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch deleted file mode 100644 index 05be7dc3b4b..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_31.patch +++ /dev/null @@ -1,2143 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4cee1f0 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() const { -+ // TODO(johnwparent): I think we can access the -+ // 2nd index of the members vec to get the long -+ // name -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetShortName() const { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetName() const { -+ std::string maybe_name = this->GetLongName(); -+ if (maybe_name.empty()) { -+ maybe_name = this->GetShortName(); -+ } -+ return maybe_name; -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..e485af0 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,9 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName() const; -+ std::string GetShortName() const; -+ std::string GetName() const; - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..a9f45ae 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran, if we cannot process the coff file -+ // we should exit with failure since something is unexpected -+ { -+ // Create temp scope to ensure all handles are appropriately deallocated -+ // since the Coff readers use RAII -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ if (!existing_coff.Parse()) { -+ std::cerr << "Unable to parse coff file: " << imp_lib_name -+ << " unable to determine import library provenance\n"; -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ std::string const shorter_name = existing_coff.GetName(); -+ std::string const link_name = basename(link_run.get_out()); -+ if (shorter_name.empty() || link_name.empty()) { -+ debug("Cannot determine either PE or COFF names (Pe: " + link_name + -+ "; Coff: " + shorter_name + ") skipping absolute rename\n"); -+ } -+ -+ if (shorter_name != link_name) { -+ debug("internal lib name: " + shorter_name + -+ " Pe name: " + link_name + " are not equivalent"); -+ return 0; -+ } -+ existing_coff_reader.Close(); -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..7340044 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); - this->implibname_ = name + ".lib"; - } -+ this->makeRsp(); -+} -+ -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << line << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {rsp_name}; -+ this->rsp_files_ = {rsp_name}; -+ return true; -+ } -+ return false; - } - - /** -@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..4f83780 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,14 +4,20 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include -+#include - #include - #include - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +33,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -337,7 +359,7 @@ std::string regexMatch( - if (!std::regex_match(searchDomain, match, reg, flag)) { - result_str = std::string(); - } else { -- result_str = match.str(); -+ result_str = match.str(1); - } - return result_str; - } -@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -591,11 +627,38 @@ std::string getSFN(const std::string& path) { - // Use "disable string parsing" prefix in case - // the path is too long - std::string const escaped = R"(\\?\)" + path; -+ // We cannot get the sfn for a path that doesn't exist -+ // if we find that the sfn we're looking for doesn't exist -+ // create a stub of the file, and allow the subsequent -+ // commands to overwrite it -+ if (!PathFileExistsA(path.c_str())) { -+ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, -+ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (h_file == INVALID_HANDLE_VALUE) { -+ debug("File " + path + -+ " does not exist, nor can it be created, unable to " -+ "compute SFN\n"); -+ CloseHandle(h_file); -+ return std::string(); -+ } -+ CloseHandle(h_file); -+ } - // Get SFN length so we can create buffer - DWORD const sfn_size = - GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT - char* sfn = new char[sfn_size + 1]; -- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ if (!res) { -+ -+ std::cerr << "Failed to process short name for " << path -+ << " Error: " << reportLastError() << "\n"; -+ } -+ if (!sfn && res) { -+ // buffer was too small -+ debug("buffer too small; had: " + std::to_string(sfn_size) + -+ " needed: " + std::to_string(res)); -+ } -+ printf("SFN: %s\n", sfn); - // sfn is null terminated per win32 api - // Ensure we strip out the disable string parsing prefix - std::string s_sfn = lstrip(sfn, R"(\\?\)"); -@@ -611,7 +674,9 @@ std::string getSFN(const std::string& path) { - */ - std::string short_name(const std::string& path) { - // Get SFN for path to name -+ debug("path: " + path); - std::string const new_abs_out = getSFN(path); -+ debug("New abs out: " + new_abs_out); - if (new_abs_out.length() > MAX_NAME_LEN) { - std::cerr << "DLL path " << path << " too long to relocate.\n"; - std::cerr << "Shortened DLL path " << new_abs_out -@@ -623,6 +688,45 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ debug("short path" + short_path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +737,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +783,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +881,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +997,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..76566eb 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -322,7 +280,9 @@ bool LibRename::ComputeDefFile() { - npos) { // Skip header in export block if still present - break; - } -- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; -+ output_file << " " -+ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") -+ << '\n'; - } - input_file.close(); - output_file.close(); -@@ -357,7 +317,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +399,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return LibRename::FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch deleted file mode 100644 index 9a0795d03dc..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_32.patch +++ /dev/null @@ -1,2143 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4cee1f0 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() const { -+ // TODO(johnwparent): I think we can access the -+ // 2nd index of the members vec to get the long -+ // name -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetShortName() const { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetName() const { -+ std::string maybe_name = this->GetLongName(); -+ if (maybe_name.empty()) { -+ maybe_name = this->GetShortName(); -+ } -+ return maybe_name; -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..e485af0 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,9 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName() const; -+ std::string GetShortName() const; -+ std::string GetName() const; - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..a9f45ae 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran, if we cannot process the coff file -+ // we should exit with failure since something is unexpected -+ { -+ // Create temp scope to ensure all handles are appropriately deallocated -+ // since the Coff readers use RAII -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ if (!existing_coff.Parse()) { -+ std::cerr << "Unable to parse coff file: " << imp_lib_name -+ << " unable to determine import library provenance\n"; -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ std::string const shorter_name = existing_coff.GetName(); -+ std::string const link_name = basename(link_run.get_out()); -+ if (shorter_name.empty() || link_name.empty()) { -+ debug("Cannot determine either PE or COFF names (Pe: " + link_name + -+ "; Coff: " + shorter_name + ") skipping absolute rename\n"); -+ } -+ -+ if (shorter_name != link_name) { -+ debug("internal lib name: " + shorter_name + -+ " Pe name: " + link_name + " are not equivalent"); -+ return 0; -+ } -+ existing_coff_reader.Close(); -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..1933591 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); - this->implibname_ = name + ".lib"; - } -+ this->makeRsp(); -+} -+ -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << escape_backslash(line) << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"@" + rsp_name}; -+ this->rsp_files_ = {"@" + rsp_name}; -+ return true; -+ } -+ return false; - } - - /** -@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..4f83780 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,14 +4,20 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include -+#include - #include - #include - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +33,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -337,7 +359,7 @@ std::string regexMatch( - if (!std::regex_match(searchDomain, match, reg, flag)) { - result_str = std::string(); - } else { -- result_str = match.str(); -+ result_str = match.str(1); - } - return result_str; - } -@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -591,11 +627,38 @@ std::string getSFN(const std::string& path) { - // Use "disable string parsing" prefix in case - // the path is too long - std::string const escaped = R"(\\?\)" + path; -+ // We cannot get the sfn for a path that doesn't exist -+ // if we find that the sfn we're looking for doesn't exist -+ // create a stub of the file, and allow the subsequent -+ // commands to overwrite it -+ if (!PathFileExistsA(path.c_str())) { -+ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, -+ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (h_file == INVALID_HANDLE_VALUE) { -+ debug("File " + path + -+ " does not exist, nor can it be created, unable to " -+ "compute SFN\n"); -+ CloseHandle(h_file); -+ return std::string(); -+ } -+ CloseHandle(h_file); -+ } - // Get SFN length so we can create buffer - DWORD const sfn_size = - GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT - char* sfn = new char[sfn_size + 1]; -- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ if (!res) { -+ -+ std::cerr << "Failed to process short name for " << path -+ << " Error: " << reportLastError() << "\n"; -+ } -+ if (!sfn && res) { -+ // buffer was too small -+ debug("buffer too small; had: " + std::to_string(sfn_size) + -+ " needed: " + std::to_string(res)); -+ } -+ printf("SFN: %s\n", sfn); - // sfn is null terminated per win32 api - // Ensure we strip out the disable string parsing prefix - std::string s_sfn = lstrip(sfn, R"(\\?\)"); -@@ -611,7 +674,9 @@ std::string getSFN(const std::string& path) { - */ - std::string short_name(const std::string& path) { - // Get SFN for path to name -+ debug("path: " + path); - std::string const new_abs_out = getSFN(path); -+ debug("New abs out: " + new_abs_out); - if (new_abs_out.length() > MAX_NAME_LEN) { - std::cerr << "DLL path " << path << " too long to relocate.\n"; - std::cerr << "Shortened DLL path " << new_abs_out -@@ -623,6 +688,45 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ debug("short path" + short_path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +737,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +783,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +881,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +997,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..76566eb 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,27 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -- -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; - } -+ PathRelocator relocator; -+ std::string const new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; -+ } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +162,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +200,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -322,7 +280,9 @@ bool LibRename::ComputeDefFile() { - npos) { // Skip header in export block if still present - break; - } -- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; -+ output_file << " " -+ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") -+ << '\n'; - } - input_file.close(); - output_file.close(); -@@ -357,7 +317,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +399,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return LibRename::FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch b/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch deleted file mode 100644 index 76af37d69bf..00000000000 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/rc_updates_33.patch +++ /dev/null @@ -1,2139 +0,0 @@ -diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml -index 9979ea1..7c106b1 100644 ---- a/.github/workflows/ci.yaml -+++ b/.github/workflows/ci.yaml -@@ -6,7 +6,6 @@ name: ci - - on: [pull_request, push] - -- - concurrency: - group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}} - cancel-in-progress: true -@@ -35,5 +34,5 @@ jobs: - - uses: actions/upload-artifact@v4 - if: always() - with: -- name: tester -- path: tmp/test/tester.exe -+ name: tests -+ path: tmp -diff --git a/.gitignore b/.gitignore -index 6a74ea7..df2004f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -19,4 +19,7 @@ - /.vscode/ - - # ignore patch files --*.patch -\ No newline at end of file -+*.patch -+ -+.clang-tidy -+.clang-format -\ No newline at end of file -diff --git a/Makefile b/Makefile -index 54646df..e5111b4 100644 ---- a/Makefile -+++ b/Makefile -@@ -29,6 +29,9 @@ BUILD_LINK = /DEBUG - BASE_CFLAGS = /EHsc - CFLAGS = $(BASE_CFLAGS) $(BUILD_CFLAGS) $(CLFLAGS) - LFLAGS = $(BUILD_LINK) $(LINKFLAGS) -+API_LIBS = Shlwapi.lib \ -+Pathcch.lib \ -+Advapi32.lib - - SRCS = cl.obj \ - execute.obj \ -@@ -55,7 +58,7 @@ linker_invocation.obj - all : install test - - cl.exe : $(SRCS) -- link $(LFLAGS) $** Shlwapi.lib /out:cl.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:cl.exe - - install : cl.exe - mkdir $(PREFIX) -@@ -66,9 +69,10 @@ install : cl.exe - mklink $(PREFIX)\relocate.exe $(PREFIX)\cl.exe - - setup_test: cl.exe -- echo "-------------------" -- echo "Running Test Setup" -- echo "-------------------" -+ @echo \n -+ @echo ------------------- -+ @echo Running Test Setup -+ @echo ------------------- - -@ if NOT EXIST "tmp\test" mkdir "tmp\test" - cd tmp\test - copy ..\..\cl.exe cl.exe -@@ -80,9 +84,9 @@ setup_test: cl.exe - # * space in a path - preserved by quoted arguments - # * escaped quoted arguments - build_and_check_test_sample : setup_test -- echo "--------------------" -- echo "Building Test Sample" -- echo "--------------------" -+ @echo -------------------- -+ @echo Building Test Sample -+ @echo -------------------- - cd tmp\test - cl /c /EHsc "..\..\test\src file\calc.cxx" /DCALC_EXPORTS /DCALC_HEADER="\"calc header/calc.h\"" /I ..\..\test\include - cl /c /EHsc ..\..\test\main.cxx /I ..\..\test\include -@@ -94,9 +98,10 @@ build_and_check_test_sample : setup_test - # Test basic wrapper behavior - did the absolute path to the DLL get injected - # into the executable - test_wrapper : build_and_check_test_sample -- echo "--------------------" -- echo "Running Wrapper Test" -- echo "--------------------" -+ @echo \n -+ @echo -------------------- -+ @echo Running Wrapper Test -+ @echo -------------------- - cd tmp - move test\tester.exe .\tester.exe - .\tester.exe -@@ -110,23 +115,25 @@ test_wrapper : build_and_check_test_sample - - # Test relocating an executable - re-write internal paths to dlls - test_relocate_exe: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate Exe Test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate Exe Test -+ @echo -------------------------- - cd tmp\test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - move calc.dll ..\calc.dll -- relocate.exe --pe tester.exe --deploy --full -- relocate.exe --pe tester.exe --export --full -+ SET SPACK_RELOCATE_PATH=$(MAKEDIR)\tmp\test\calc.dll|$(MAKEDIR)\tmp\calc.dll -+ relocate.exe --pe tester.exe --full - tester.exe - move ..\calc.dll calc.dll - cd ../.. - - # Test relocating a dll - re-write import library - test_relocate_dll: build_and_check_test_sample -- echo "--------------------------" -- echo "Running Relocate DLL test" -- echo "--------------------------" -+ @echo \n -+ @echo -------------------------- -+ @echo Running Relocate DLL test -+ @echo -------------------------- - cd tmp/test - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -134,38 +141,51 @@ test_relocate_dll: build_and_check_test_sample - mkdir tmp_lib - move test\calc.dll tmp_bin\calc.dll - move test\calc.lib tmp_lib\calc.lib -- test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib --export -+ test\relocate.exe --pe tmp_bin\calc.dll --coff tmp_lib\calc.lib - cd test - del tester.exe - link main.obj ..\tmp_lib\calc.lib /out:tester.exe - .\tester.exe - cd ../.. - --test_pipe_overflow: build_and_check_test_sample -- echo "--------------------" -- echo " Pipe overflow test" -- echo "--------------------" -+test_pipe_out_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stdout overflow test -+ @echo --------------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\test\lots-of-output.bat - cl /c /EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - -+test_pipe_error_overflow: build_and_check_test_sample -+ @echo \n -+ @echo --------------------------- -+ @echo Pipe stderr overflow test -+ @echo --------------------------- -+ set SPACK_CC_TMP=%SPACK_CC% -+ set SPACK_CC=$(MAKEDIR)\test\lots-of-error.bat -+ cl /c /EHsc "test\src file\calc.cxx" -+ set SPACK_CC=%SPACK_CC_TMP% -+ - build_zerowrite_test: test\writezero.obj -- link $(LFLAGS) $** Shlwapi.lib /out:writezero.exe -+ link $(LFLAGS) $** $(API_LIBS) /out:writezero.exe - - test_zerowrite: build_zerowrite_test -- echo "-----------------------" -- echo "Running zerowrite test" -- echo "-----------------------" -+ @echo \n -+ @echo ----------------------- -+ @echo Running zerowrite test -+ @echo ----------------------- - set SPACK_CC_TMP=%SPACK_CC% - set SPACK_CC=$(MAKEDIR)\writezero.exe - cl /c EHsc "test\src file\calc.cxx" - set SPACK_CC=%SPACK_CC_TMP% - - test_long_paths: build_and_check_test_sample -- echo "------------------------" -- echo "Running long paths test" -- echo "------------------------" -+ @echo \n -+ @echo ------------------------ -+ @echo Running long paths test -+ @echo ------------------------ - mkdir tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E test\include tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - xcopy /E "test\src file" tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname -@@ -182,9 +202,10 @@ test_long_paths: build_and_check_test_sample - cd ../../../.. - - test_relocate_long_paths: test_long_paths -- echo "---------------------------------" -- echo "Running relocate logn paths test" -- echo "---------------------------------" -+ @echo \n -+ @echo --------------------------------- -+ @echo Running relocate logn paths test -+ @echo --------------------------------- - cd tmp\tmp\verylongdirectoryname\evenlongersubdirectoryname - -@ if NOT EXIST "relocate.exe" mklink relocate.exe cl.exe - cd .. -@@ -192,7 +213,7 @@ test_relocate_long_paths: test_long_paths - mkdir tmp_lib - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll - move evenlongersubdirectoryname\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib -- evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib --export -+ evenlongersubdirectoryname\relocate.exe --pe tmp_bin\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.dll --coff tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib - cd evenlongersubdirectoryname - del tester.exe - link main.obj ..\tmp_lib\verylongfilepathnamethatwilldefinitelybegreaterthanonehundredandfourtyfourcharacters.lib /out:tester.exe -@@ -200,9 +221,10 @@ test_relocate_long_paths: test_long_paths - cd ../../../.. - - test_exe_with_exports: -- echo ------------------------------ -- echo Running exe with exports test -- echo ------------------------------ -+ @echo \n -+ @echo ------------------------------ -+ @echo Running exe with exports test -+ @echo ------------------------------ - mkdir tmp\test\exe_with_exports - xcopy /E test\include tmp\test\exe_with_exports - xcopy /E "test\src file" tmp\test\exe_with_exports -@@ -223,6 +245,10 @@ test_exe_with_exports: - cd ../../.. - - test_def_file_name_override: -+ @echo -+ @echo ------------------------------------ -+ @echo Running Def file name override test -+ @echo ------------------------------------ - mkdir tmp\test\def\def_override - xcopy /E test\include tmp\test\def\def_override - xcopy /E "test\src file" tmp\test\def\def_override -@@ -241,7 +267,7 @@ test_def_file_name_override: - test_and_cleanup: test clean-test - - --test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_overflow -+test: test_wrapper test_relocate_exe test_relocate_dll test_def_file_name_override test_exe_with_exports test_long_paths test_pipe_out_overflow test_pipe_error_overflow - - - clean : clean-test clean-cl -diff --git a/src/coff_parser.cxx b/src/coff_parser.cxx -index 25f4b28..4cee1f0 100644 ---- a/src/coff_parser.cxx -+++ b/src/coff_parser.cxx -@@ -46,8 +46,9 @@ CoffParser::CoffParser(CoffReaderWriter* coff_reader) - */ - bool CoffParser::Parse() { - if (!this->coffStream_->Open()) { -- std::cerr << "Unable to open coff file for reading: " -- << reportLastError() << "\n"; -+ std::cerr << "Unable to open coff file: " -+ << this->coffStream_->get_file() -+ << " for reading: " << reportLastError() << "\n"; - return false; - } - int const invalid_valid_sig = -@@ -578,6 +579,40 @@ void CoffParser::ReportLongName(const char* data) { - std::cout << "DLL: " << data << "\n"; - } - -+std::string CoffParser::GetLongName() const { -+ // TODO(johnwparent): I think we can access the -+ // 2nd index of the members vec to get the long -+ // name -+ for (auto mem : this->coff_.members) { -+ if (mem.member->is_longname) { -+ return std::string(mem.member->data); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetShortName() const { -+ for (auto mem : this->coff_.members) { -+ if (mem.header->Name[0] != '/') { -+ int i = 0; -+ while (mem.header->Name[i] != '/') { -+ ++i; -+ } -+ return std::string(reinterpret_cast(mem.header->Name), -+ i); -+ } -+ } -+ return std::string(); -+} -+ -+std::string CoffParser::GetName() const { -+ std::string maybe_name = this->GetLongName(); -+ if (maybe_name.empty()) { -+ maybe_name = this->GetShortName(); -+ } -+ return maybe_name; -+} -+ - void CoffParser::Report() { - for (auto mem : this->coff_.members) { - if (mem.member->is_longname) { -diff --git a/src/coff_parser.h b/src/coff_parser.h -index 6cf7728..e485af0 100644 ---- a/src/coff_parser.h -+++ b/src/coff_parser.h -@@ -43,6 +43,9 @@ class CoffParser { - bool NormalizeName(std::string& name); - void Report(); - int Verify(); -+ std::string GetLongName() const; -+ std::string GetShortName() const; -+ std::string GetName() const; - static int Validate(std::string& coff); - }; - -diff --git a/src/coff_reader_writer.cxx b/src/coff_reader_writer.cxx -index cbab86f..6e34736 100644 ---- a/src/coff_reader_writer.cxx -+++ b/src/coff_reader_writer.cxx -@@ -6,6 +6,7 @@ - - #include "coff_reader_writer.h" - #include "coff.h" -+#include "utils.h" - - #include - #include -@@ -13,16 +14,31 @@ - #include - #include - #include -+#include - #include -+#include - #include - - CoffReaderWriter::CoffReaderWriter(std::string const& file) - : file_(std::move(file)) {} - - bool CoffReaderWriter::Open() { -- this->pe_stream_.open(this->file_, -- std::ios::in | std::ios::out | std::ios::binary); -- return this->pe_stream_.is_open(); -+ std::wstring coff_file; -+ try { -+ coff_file = ConvertASCIIToWide(this->file_); -+ } catch (std::overflow_error& e) { -+ return false; -+ } -+ try { -+ ScopedFileAccess const obtain_write(coff_file, GENERIC_ALL); -+ this->pe_stream_.open(this->file_, -+ std::ios::in | std::ios::out | std::ios::binary); -+ return this->pe_stream_.is_open(); -+ } catch (std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; -+ return false; -+ } - } - - bool CoffReaderWriter::Close() { -diff --git a/src/commandline.cxx b/src/commandline.cxx -index 38bd760..df2bff1 100644 ---- a/src/commandline.cxx -+++ b/src/commandline.cxx -@@ -86,14 +86,6 @@ bool print_help() { - "said library is regenerated and the old imp lib\n"; - std::cout << " " - "replaced.\n"; -- std::cout << " --export|--deploy = " -- "Mutually exclusive command modifier.\n"; -- std::cout << " " -- "Instructs relocate to either prepare the\n"; -- std::cout << " " -- "dynamic library for exporting to build cache\n"; -- std::cout << " " -- "or for extraction from bc onto new host system\n"; - std::cout << " --report = " - "Report information about the parsed PE/Coff files\n"; - std::cout << " --debug|-d = " -@@ -149,24 +141,6 @@ std::map ParseRelocate(const char** args, int argc) { - return opts; - } - opts.insert(std::pair("full", "full")); -- } else if (!strcmp(args[i], "--export")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "export")); -- } else if (!strcmp(args[i], "--deploy")) { -- // export and deploy are mutually exclusive, if one is defined -- // the other cannot be -- if (redefinedArgCheck(opts, "export", "--export") || -- redefinedArgCheck(opts, "deploy", "--deploy")) { -- opts.clear(); -- return opts; -- } -- opts.insert(std::pair("cmd", "deploy")); - } else if (!strcmp(args[i], "--debug") || !strcmp(args[i], "-d")) { - opts.insert(std::pair("debug", "on")); - } else if (!strcmp(args[i], "--verify")) { -diff --git a/src/execute.cxx b/src/execute.cxx -index b00bf1d..b4f8376 100644 ---- a/src/execute.cxx -+++ b/src/execute.cxx -@@ -31,6 +31,8 @@ enum : std::uint16_t { InvalidExitCode = 999 }; - ExecuteCommand::ExecuteCommand(std::string command) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(command)) { - this->CreateChildPipes(); - this->SetupExecute(); -@@ -39,6 +41,8 @@ ExecuteCommand::ExecuteCommand(std::string command) - ExecuteCommand::ExecuteCommand(std::string arg, const StrList& args) - : ChildStdOut_Rd(nullptr), - ChildStdOut_Wd(nullptr), -+ ChildStdErr_Rd(nullptr), -+ ChildStdErr_Wd(nullptr), - base_command(std::move(arg)) { - for (const auto& argp : args) { - this->command_args.push_back(argp); -@@ -51,6 +55,8 @@ ExecuteCommand& ExecuteCommand::operator=( - ExecuteCommand&& execute_command) noexcept { - this->ChildStdOut_Rd = std::move(execute_command.ChildStdOut_Rd); - this->ChildStdOut_Wd = std::move(execute_command.ChildStdOut_Wd); -+ this->ChildStdErr_Rd = std::move(execute_command.ChildStdErr_Rd); -+ this->ChildStdErr_Wd = std::move(execute_command.ChildStdErr_Wd); - this->procInfo = std::move(execute_command.procInfo); - this->startInfo = std::move(execute_command.startInfo); - this->saAttr = std::move(execute_command.saAttr); -@@ -59,6 +65,7 @@ ExecuteCommand& ExecuteCommand::operator=( - this->base_command = std::move(execute_command.base_command); - this->command_args = std::move(execute_command.command_args); - this->child_out_future = std::move(execute_command.child_out_future); -+ this->child_err_future = std::move(execute_command.child_err_future); - this->exit_code_future = std::move(exit_code_future); - return *this; - } -@@ -76,7 +83,7 @@ void ExecuteCommand::SetupExecute() { - // This structure specifies the STDIN and STDOUT handles for redirection. - ZeroMemory(&si_start_info, sizeof(STARTUPINFOW)); - si_start_info.cb = sizeof(STARTUPINFOW); -- si_start_info.hStdError = this->ChildStdOut_Wd; -+ si_start_info.hStdError = this->ChildStdErr_Wd; - si_start_info.hStdOutput = this->ChildStdOut_Wd; - si_start_info.dwFlags |= STARTF_USESTDHANDLES; - this->procInfo = pi_proc_info; -@@ -162,6 +169,7 @@ bool ExecuteCommand::ExecuteToolChainChild() { - // determine when child proc is done - free(nc_command_line); - CloseHandle(this->ChildStdOut_Wd); -+ CloseHandle(this->ChildStdErr_Wd); - return true; - } - -diff --git a/src/ld.cxx b/src/ld.cxx -index 5cc0683..a9f45ae 100644 ---- a/src/ld.cxx -+++ b/src/ld.cxx -@@ -6,6 +6,9 @@ - #include "ld.h" - #include - #include -+#include -+#include -+#include - #include - #include "coff_parser.h" - #include "coff_reader_writer.h" -@@ -19,21 +22,51 @@ void LdInvocation::LoadToolchainDependentSpackVars(SpackEnvState& spackenv) { - } - - DWORD LdInvocation::InvokeToolchain() { -+ // Run a pass of the linker -+ -+ // First parse the linker command line to -+ // understand what we'll be doing -+ LinkerInvocation link_run(this->inputs); -+ link_run.Parse(); -+ std::string rc_file; -+ try { -+ // Run resource compiler to create -+ // Resource for id'ing binary when relocating its import library -+ rc_file = LdInvocation::createRC(link_run); -+ } catch (const RCCompilerFailure& e) { -+ return ExitConditions::TOOLCHAIN_FAILURE; -+ } -+ -+ // Add produced RC file to linker CLI to inject ID -+ // This needs to be at the end of either libs or objs or rsp files -+ // so long as this RC file is not the first binary -+ // file the linker sees (or is referenced in the case of an rsp) -+ // otherwise this resource file will dictate the binairies -+ // name, which will break client expectations -+ this->inputs.push_back(rc_file); - // Run base linker invocation to produce initial - // dll and import library - DWORD const ret_code = ToolChainInvocation::InvokeToolchain(); - if (ret_code != 0) { - return ret_code; - } -+ -+ // We're creating a PE, we need to create an appropriate import lib -+ std::string const imp_lib_name = link_run.get_implib_name(); -+ - // Next we want to construct the proper commmand line to - // recreate the import library from the same set of obj files - // and libs -- LinkerInvocation link_run(LdInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- link_run.Parse(); -- // We're creating a PE, we need to create an appropriate import lib -- std::string const imp_lib_name = link_run.get_implib_name(); -+ -+ // first determine if this link run created the import library -+ // check if the import library that *might* be produced -+ // by this run (given input argument construction) -+ // exists. Multiple link runs could in theory produce the name -+ // imp lib (or at least with the same name) -+ // i.e. link /out:perl.exe perl.lib -+ // and link /out:perl.dll perl.lib /DLL could both in theory -+ // produce the same import library -+ - // If there is no implib, we don't need to bother - // trying to rename - if (!fileExists(imp_lib_name)) { -@@ -43,6 +76,35 @@ DWORD LdInvocation::InvokeToolchain() { - // the concern of this wrapper - return 0; - } -+ // There is an imp lib, so -+ // Check if the imp lib is associated with the link command -+ // we just ran, if we cannot process the coff file -+ // we should exit with failure since something is unexpected -+ { -+ // Create temp scope to ensure all handles are appropriately deallocated -+ // since the Coff readers use RAII -+ CoffReaderWriter existing_coff_reader(imp_lib_name); -+ CoffParser existing_coff(&existing_coff_reader); -+ if (!existing_coff.Parse()) { -+ std::cerr << "Unable to parse coff file: " << imp_lib_name -+ << " unable to determine import library provenance\n"; -+ return ExitConditions::COFF_PARSE_FAILURE; -+ } -+ std::string const shorter_name = existing_coff.GetName(); -+ std::string const link_name = basename(link_run.get_out()); -+ if (shorter_name.empty() || link_name.empty()) { -+ debug("Cannot determine either PE or COFF names (Pe: " + link_name + -+ "; Coff: " + shorter_name + ") skipping absolute rename\n"); -+ } -+ -+ if (shorter_name != link_name) { -+ debug("internal lib name: " + shorter_name + -+ " Pe name: " + link_name + " are not equivalent"); -+ return 0; -+ } -+ existing_coff_reader.Close(); -+ } -+ - std::string pe_name; - try { - pe_name = link_run.get_mangled_out(); -@@ -52,20 +114,15 @@ DWORD LdInvocation::InvokeToolchain() { - return ExitConditions::NORMALIZE_NAME_FAILURE; - } - std::string const abs_out_imp_lib_name = imp_lib_name + ".pe-abs.lib"; -- std::string const def_file = -- link_run.get_def_file().empty() ? " " : ":" + link_run.get_def_file(); -- std::string const def = "-def" + def_file; -+ std::string const def_file = link_run.get_def_file(); -+ std::string const def = "-def" + (def_file.empty() ? " " : ":" + def_file); - std::string piped_args = link_run.get_lib_link_args(); - // create command line to generate new import lib -- this->rpath_executor = -- ExecuteCommand("lib.exe", LdInvocation::ComposeCommandLists({ -- {def, piped_args, "-name:" + pe_name, -- "-out:" + abs_out_imp_lib_name}, -- {link_run.get_rsp_file()}, -- this->obj_args, -- this->lib_args, -- this->lib_dir_args, -- })); -+ this->rpath_executor = ExecuteCommand( -+ "lib.exe", -+ LdInvocation::ComposeCommandLists({{def, piped_args, "-name:" + pe_name, -+ "-out:" + abs_out_imp_lib_name}, -+ link_run.get_input_files()})); - this->rpath_executor.Execute(); - DWORD const err_code = this->rpath_executor.Join(); - if (err_code != 0) { -@@ -73,10 +130,13 @@ DWORD LdInvocation::InvokeToolchain() { - } - CoffReaderWriter coff_reader(abs_out_imp_lib_name); - CoffParser coff(&coff_reader); -+ debug("Parsing COFF file: " + abs_out_imp_lib_name); - if (!coff.Parse()) { - debug("Failed to parse COFF file: " + abs_out_imp_lib_name); - return ExitConditions::COFF_PARSE_FAILURE; - } -+ debug("COFF file parsed"); -+ debug("Normalizing coff file for name: " + pe_name); - if (!coff.NormalizeName(pe_name)) { - debug("Failed to normalize name for COFF file: " + - abs_out_imp_lib_name); -@@ -99,3 +159,48 @@ DWORD LdInvocation::InvokeToolchain() { - } - return ret_code; - } -+ -+std::string LdInvocation::createRC(LinkerInvocation& link_run) { -+ const std::string pe_stage_name = link_run.get_out(); -+ const std::string template_base = -+ "spack SPACKRESOURCE\n" -+ "BEGIN\n"; -+ const std::string template_end = "END\n"; -+ const std::string pe_name = stripLastExt(basename(pe_stage_name)); -+ const std::string rc_file_name = "spack-" + pe_name + ".rc"; -+ // This res file name needs to mirror the PE name _exactly_ -+ // Otherwise the RC file will override the default -+ // or user set name, violating user expectation -+ std::string res_file_name = pe_name + ".res"; -+ if (!link_run.get_rc_files().empty()) { -+ res_file_name = "spack-" + res_file_name; -+ } -+ -+ ExecuteCommand rc_executor("rc", -+ {"/fo" + res_file_name + " " + rc_file_name}); -+ std::ofstream rc_out(rc_file_name); -+ if (!rc_out) { -+ std::cerr << "Error: could not open rc file for creation: " -+ << rc_file_name << "\n"; -+ throw RCCompilerFailure("Could not open RC file"); -+ } -+ std::string abs_out = EnsureValidLengthPath( -+ CannonicalizePath(MakePathAbsolute(pe_stage_name))); -+ char* chr_abs_out = new char[abs_out.length() + 1]; -+ strcpy(chr_abs_out, abs_out.c_str()); -+ char* padded_path = -+ pad_path(chr_abs_out, static_cast(abs_out.length()), '\\'); -+ abs_out = std::string(padded_path, MAX_NAME_LEN); -+ free(chr_abs_out); -+ free(padded_path); -+ abs_out = escape_backslash(abs_out); -+ rc_out << template_base << " " << '"' << abs_out << '"' << "\n" -+ << template_end; -+ rc_out.close(); -+ rc_executor.Execute(); -+ DWORD const err_code = rc_executor.Join(); -+ if (err_code != 0) { -+ throw RCCompilerFailure("Could not compile RC file"); -+ } -+ return res_file_name; -+} -diff --git a/src/ld.h b/src/ld.h -index a9f3a82..191aaba 100644 ---- a/src/ld.h -+++ b/src/ld.h -@@ -5,6 +5,8 @@ - */ - #pragma once - -+#include "linker_invocation.h" -+ - #include "toolchain.h" - - /** -@@ -20,4 +22,5 @@ class LdInvocation : public ToolChainInvocation { - void LoadToolchainDependentSpackVars(SpackEnvState& spackenv); - std::string lang = "link"; - ExecuteCommand rpath_executor; -+ static std::string createRC(LinkerInvocation& link_run); - }; -diff --git a/src/linker_invocation.cxx b/src/linker_invocation.cxx -index 4ed82be..1933591 100644 ---- a/src/linker_invocation.cxx -+++ b/src/linker_invocation.cxx -@@ -3,14 +3,19 @@ - * - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ -+#include - #include - #include - #include - #include - #include -+#include - #include "linker_invocation.h" -+#include - #include "utils.h" - -+enum { MaxProcessCommandLength = 32767 }; -+ - /** - * Parses the command line of a given linker invocation and stores information - * about that command line and its associated behavior -@@ -46,21 +51,31 @@ void LinkerInvocation::Parse() { - // len 2 - StrList implib_line = split(*token, ":"); - this->implibname_ = implib_line[1]; -- } else if (endswith(normal_token, ".lib")) { -- this->libs_.push_back(*token); - } else if (normal_token == "dll") { - this->is_exe_ = false; - } else if (startswith(normal_token, "out")) { - this->output_ = split(*token, ":")[1]; -- } else if (endswith(normal_token, ".obj")) { -- this->objs_.push_back(*token); -- } else if (startswith(normal_token, "@") && -- endswith(normal_token, ".rsp")) { -+ } else if (endswith(normal_token, ".obj") || -+ endswith(normal_token, ".lib") || -+ endswith(normal_token, ".lo")) { -+ this->input_files_.push_back(*token); -+ } else if (startswith(normal_token, "@")) { - // RSP files are used to describe object files, libraries, other CLI - // Switches relevant to the tool the rsp file is being passed to - // Primarily utilized by CMake and MSBuild projects to bypass - // Command line length limits -- this->rsp_file_ = *token; -+ this->rsp_files_.push_back(*token); -+ // Since rsp files are essentially expanded in place on the command line -+ // i.e objA rspA objC -+ // where rspA defines objB the cli would then be -+ // objA objB objC -+ // so we also need to track them in binary_files_ since the order -+ // of their expansion has implications for naming, i.e -+ // if rspA was the first input file, the dll/imp name would be objB -+ this->input_files_.push_back(*token); -+ } else if (endswith(normal_token, ".res")) { -+ this->rc_files_.push_back(*token); -+ this->input_files_.push_back(*token); - } else if (startswith(normal_token, "def")) { - this->def_file_ = strip(split(*token, ":", 1)[1], "\""); - } else if (this->piped_args_.find(normal_token) != -@@ -68,29 +83,109 @@ void LinkerInvocation::Parse() { - this->piped_args_.at(normal_token).emplace_back(*token); - } - } -- // If we have a def file and no name, attempt to -- // scrape the def file for a name to be sure -- // we respect the intended project name -- // vs overriding via the CLI -- if (!this->def_file_.empty() && this->output_.empty()) { -- this->processDefFile(); -- } -+ -+ // Note for the below: name will never be specified so we only have -+ // /out, .def files, and input files -+ // To determine internal dll name -+ // If a def file was not specified: -+ // /name is used -+ // if no /name /out is used -+ // if no /out or /name use first input file -+ -+ // If a def file was specified: -+ // LIBRARY -+ // otherwise fallback to previous -+ -+ // To determine output name -+ // /OUT is always overriding -+ // If not /OUT and .def file: -+ // LIBRARY -+ // if no def or no LIBRARY -+ // /NAME -+ // if no /NAME -+ // first input file (post rc expanion) -+ -+ this->processDefFile(); -+ this->processInputFiles(); - std::string const ext = this->is_exe_ ? ".exe" : ".dll"; -- // If output wasn't defined on the command line -- // or the def file -- // compute it based on the same logic as the linker -- // i.e. first obj file name - if (this->output_.empty()) { - // with no "out" argument, the linker - // will place the file in the CWD -- std::string const name_obj = this->objs_.front(); -- std::string const filename = split(name_obj, "\\").back(); -- this->output_ = join({GetCWD(), strip(filename, ".obj")}, "\\") + ext; -+ std::string const name_component = this->input_files_.front(); -+ std::string const filename = split(name_component, "\\").back(); -+ this->output_ = join({GetCWD(), stripLastExt(filename)}, "\\") + ext; - } - if (this->implibname_.empty()) { - std::string const name = strip(this->output_, ext); - this->implibname_ = name + ".lib"; - } -+ this->makeRsp(); -+} -+ -+void LinkerInvocation::processInputFiles() { -+ StrList new_input_files; -+ for (auto input = this->input_files_.begin(); -+ input != this->input_files_.end(); ++input) { -+ if (startswith(*input, "@")) { -+ // rsp file - expand contents in input files -+ // list in place and remove self -+ StrList rsp_inputs = LinkerInvocation::processRSPFile(*input); -+ new_input_files.insert(new_input_files.end(), rsp_inputs.begin(), -+ rsp_inputs.end()); -+ } else { -+ new_input_files.push_back(*input); -+ } -+ } -+ this->input_files_ = new_input_files; -+} -+ -+StrList LinkerInvocation::processRSPFile(std::string const& rsp_file) { -+ std::string const rsp_file_in = lstrip(rsp_file, "@"); -+ std::ifstream rsp_stream(rsp_file_in); -+ if (!rsp_stream) { -+ std::cerr << "Error: Could not open input rsp file: " << rsp_file_in -+ << "\n"; -+ throw FileIOError("Cannot open rsp input file: " + GetLastError()); -+ } -+ StrList inputs; -+ std::string line; -+ while (std::getline(rsp_stream, line)) { -+ std::stringstream rsp_line(line); -+ std::string input_file; -+ rsp_line >> input_file; -+ inputs.push_back(input_file); -+ } -+ return inputs; -+} -+ -+/** -+ * \brief Ensure command line given to lib.exe is of appropriate length -+ * max windows createProcess command line length is 32,767, so if we exceed -+ * that, compose all input file args into an rsp. -+ * -+ * Writes an rsp file named spack-build.rsp and sets it to be the only -+ * input file for the lib tool -+ */ -+bool LinkerInvocation::makeRsp() { -+ int const total_length = std::accumulate( -+ this->input_files_.begin(), this->input_files_.end(), 0, -+ [](size_t sum, const std::string& s) { return sum + s.size(); }); -+ if (total_length > MaxProcessCommandLength) { -+ std::string const rsp_name = "spack-build.rsp"; -+ std::ofstream rsp_out(rsp_name); -+ if (!rsp_out) { -+ std::cerr << "Unable to open rsp out file: spack-build.rsp\n"; -+ throw FileIOError("Unable to open lib rsp file"); -+ } -+ for (const auto& line : this->input_files_) { -+ rsp_out << escape_backslash(line) << "\n"; -+ } -+ rsp_out.close(); -+ this->input_files_ = {"@" + rsp_name}; -+ this->rsp_files_ = {"@" + rsp_name}; -+ return true; -+ } -+ return false; - } - - /** -@@ -104,11 +199,15 @@ void LinkerInvocation::Parse() { - */ - void LinkerInvocation::processDefFile() { - -+ if (this->def_file_.empty()) { -+ return; -+ } - // Def from link line - std::ifstream def_in(this->def_file_); - if (!def_in) { - std::cerr << "Error: Could not open input def file: " << this->def_file_ - << "\n"; -+ throw FileIOError("Cannot open def input file: " + GetLastError()); - } - - std::string line; -@@ -131,18 +230,22 @@ void LinkerInvocation::processDefFile() { - if (keyword == "NAME") { - this->is_exe_ = true; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".exe"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".exe"; - def_file_export_name = true; - } else if (keyword == "LIBRARY") { - this->is_exe_ = false; - def_line >> this->pe_name_; -- this->pe_name_ = this->pe_name_ + ".dll"; -+ this->pe_name_ = stripquotes(this->pe_name_) + ".dll"; - def_file_export_name = true; - } else { - exports.push_back(line); - } - } - if (def_file_export_name) { -+ // if output is not specified on the command line, this defines the output name -+ if (this->output_.empty()) { -+ this->output_ = join({GetCWD(), this->pe_name_}, "\\"); -+ } - const std::string def_name = stem(this->def_file_); - const std::string def_path = - this->def_file_.substr(0, this->def_file_.find(def_name)); -@@ -152,6 +255,7 @@ void LinkerInvocation::processDefFile() { - if (!def_out) { - std::cerr << "Error: could not open output def file: " << rename_def - << "\n"; -+ throw FileIOError("Cannot open def output file: " + GetLastError()); - } - for (const auto& line : exports) { - def_out << line << "\n"; -@@ -162,11 +266,11 @@ void LinkerInvocation::processDefFile() { - def_in.close(); - } - --std::string LinkerInvocation::get_implib_name() { -+std::string LinkerInvocation::get_implib_name() const { - return this->implibname_; - } - --std::string LinkerInvocation::get_lib_link_args() { -+std::string LinkerInvocation::get_lib_link_args() const { - std::string lib_link_line; - for (const auto& var_args : this->piped_args_) { - // Most of these should be single arguments -@@ -182,22 +286,30 @@ std::string LinkerInvocation::get_lib_link_args() { - return lib_link_line; - } - --std::string LinkerInvocation::get_def_file() { -+std::string LinkerInvocation::get_def_file() const { - return this->def_file_; - } - --std::string LinkerInvocation::get_rsp_file() { -- return this->rsp_file_; -+StrList LinkerInvocation::get_rsp_files() const { -+ return this->rsp_files_; -+} -+ -+StrList LinkerInvocation::get_rc_files() const { -+ return this->rc_files_; -+} -+ -+StrList LinkerInvocation::get_input_files() const { -+ return this->input_files_; - } - --std::string LinkerInvocation::get_out() { -- return this->pe_name_.empty() ? this->output_ : this->pe_name_; -+std::string LinkerInvocation::get_out() const { -+ return this->output_; - } - --std::string LinkerInvocation::get_mangled_out() { -+std::string LinkerInvocation::get_mangled_out() const { - return mangle_name(this->get_out()); - } - --bool LinkerInvocation::IsExeLink() { -+bool LinkerInvocation::IsExeLink() const { - return this->is_exe_ || endswith(this->get_out(), ".exe"); - } -diff --git a/src/linker_invocation.h b/src/linker_invocation.h -index a92a538..207feb0 100644 ---- a/src/linker_invocation.h -+++ b/src/linker_invocation.h -@@ -15,25 +15,31 @@ class LinkerInvocation { - explicit LinkerInvocation(const StrList& linkline); - ~LinkerInvocation() = default; - void Parse(); -- bool IsExeLink(); -- std::string get_out(); -- std::string get_mangled_out(); -- std::string get_implib_name(); -- std::string get_def_file(); -- std::string get_rsp_file(); -- std::string get_lib_link_args(); -+ bool IsExeLink() const; -+ std::string get_out() const; -+ std::string get_mangled_out() const; -+ std::string get_implib_name() const; -+ std::string get_def_file() const; -+ StrList get_rsp_files() const; -+ StrList get_rc_files() const; -+ StrList get_input_files() const; -+ std::string get_lib_link_args() const; -+ bool makeRsp(); - - private: - void processDefFile(); -+ void processInputFiles(); -+ static StrList processRSPFile(std::string const& rsp_file); - std::string line_; -- StrList tokens_; - std::string pe_name_; - std::string implibname_; - std::string def_file_; -- std::string rsp_file_; - std::string output_; -- StrList libs_; -- StrList objs_; -+ StrList rsp_files_; -+ StrList rc_files_; -+ StrList command_files_; -+ StrList input_files_; -+ StrList tokens_; - bool is_exe_; - std::map piped_args_ = { - {"export", {}}, {"include", {}}, {"libpath", {}}, -diff --git a/src/main.cxx b/src/main.cxx -index 1fb0b63..7fa7708 100644 ---- a/src/main.cxx -+++ b/src/main.cxx -@@ -33,8 +33,6 @@ int main(int argc, const char* argv[]) { - return -1; - } - bool const full = !(patch_args.find("full") == patch_args.end()); -- bool const deploy = !(patch_args.find("cmd") == patch_args.end()) && -- patch_args.at("cmd") == "deploy"; - bool const report = !(patch_args.find("report") == patch_args.end()); - bool const has_pe = !(patch_args.find("pe") == patch_args.end()); - bool const debug = !(patch_args.find("debug") == patch_args.end()); -@@ -77,12 +75,11 @@ int main(int argc, const char* argv[]) { - std::unique_ptr rpath_lib; - try { - if (has_coff) { -- rpath_lib = std::make_unique(patch_args.at("pe"), -- patch_args.at("coff"), -- full, deploy, true); -+ rpath_lib = std::make_unique( -+ patch_args.at("pe"), patch_args.at("coff"), full, true); - } else { - rpath_lib = std::make_unique(patch_args.at("pe"), -- full, deploy, true); -+ full, true); - } - } catch (const NameTooLongError& e) { - std::cerr << "Cannot Rename PE file " << patch_args.at("pe") -@@ -104,8 +101,8 @@ int main(int argc, const char* argv[]) { - } - if (report_args.find("pe") != report_args.end()) { - try { -- LibRename portable_executable( -- report_args.at("pe"), std::string(), false, false, true); -+ LibRename portable_executable(report_args.at("pe"), -+ std::string(), false, true); - portable_executable.ExecuteRename(); - } catch (const NameTooLongError& e) { - std::cerr -diff --git a/src/toolchain.cxx b/src/toolchain.cxx -index a1c103f..1d3318e 100644 ---- a/src/toolchain.cxx -+++ b/src/toolchain.cxx -@@ -15,7 +15,7 @@ - - ToolChainInvocation::ToolChainInvocation(std::string command, - char const* const* cli) -- : command(std::move(std::move(command))) { -+ : command(std::move(command)) { - this->ParseCommandArgs(cli); - } - -@@ -23,10 +23,10 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - // inject Spack includes before the default includes - for (auto& include : spackenv.SpackIncludeDirs) { - auto inc_arg = ToolChainInvocation::ComposeIncludeArg(include); -- this->include_args.insert(this->include_args.begin(), inc_arg); -+ this->inputs.push_back(inc_arg); - } - for (auto& lib : spackenv.SpackLdLibs) { -- this->lib_args.push_back(lib); -+ this->inputs.push_back(lib); - } - this->AddExtraLibPaths(spackenv.SpackLinkDirs); - this->AddExtraLibPaths(spackenv.SpackRPathDirs); -@@ -36,10 +36,8 @@ void ToolChainInvocation::InterpolateSpackEnv(SpackEnvState& spackenv) { - } - - DWORD ToolChainInvocation::InvokeToolchain() { -- StrList const command_line(ToolChainInvocation::ComposeCommandLists( -- {this->command_args, this->include_args, this->lib_args, -- this->lib_dir_args, this->obj_args})); -- this->executor = ExecuteCommand(this->command, command_line); -+ quoteList(this->inputs); -+ this->executor = ExecuteCommand(this->command, this->inputs); - debug("Setting up executor for " + std::string(typeid(*this).name()) + - "toolchain"); - debug("Toolchain: " + this->command); -@@ -53,38 +51,9 @@ DWORD ToolChainInvocation::InvokeToolchain() { - } - - void ToolChainInvocation::ParseCommandArgs(char const* const* cli) { -- // Collect include args as we need to ensure Spack -- // Includes come first - for (char const* const* co = cli; *co; co++) { -- std::string norm_arg = std::string(*co); - const std::string arg = std::string(*co); -- lower(norm_arg); -- if (isCommandArg(norm_arg, "i")) { -- // We have an include arg -- // can have an optional space -- // check if there are characters after -- // "/I" and if not we consider the next -- // argument to be the include -- if (arg.size() > 2) -- this->include_args.push_back(arg); -- else { -- this->include_args.push_back(arg); -- this->include_args.emplace_back(*(++co)); -- } -- } else if (endswith(norm_arg, ".lib") && -- (norm_arg.find("implib:") == std::string::npos)) -- // Lib args are just libraries -- // provided like system32.lib on the -- // command line. -- // lib specification order does not matter -- // on MSVC but this is useful for filtering system libs -- // and adding all libs -- this->lib_args.push_back(arg); -- else if (endswith(norm_arg, ".obj")) -- this->obj_args.push_back(arg); -- else { -- this->command_args.push_back(arg); -- } -+ this->inputs.push_back(arg); - } - } - -@@ -98,8 +67,7 @@ std::string ToolChainInvocation::ComposeLibPathArg(std::string& libPath) { - - void ToolChainInvocation::AddExtraLibPaths(StrList paths) { - for (auto& lib_dir : paths) { -- this->lib_dir_args.push_back( -- ToolChainInvocation::ComposeLibPathArg(lib_dir)); -+ this->inputs.push_back(ToolChainInvocation::ComposeLibPathArg(lib_dir)); - } - } - -diff --git a/src/toolchain.h b/src/toolchain.h -index ec80e2b..c702420 100644 ---- a/src/toolchain.h -+++ b/src/toolchain.h -@@ -32,10 +32,7 @@ class ToolChainInvocation { - - std::string command; - std::string lang; -- StrList command_args; -- StrList include_args; -- StrList lib_dir_args; -- StrList lib_args; -- StrList obj_args; -+ StrList inputs; -+ - ExecuteCommand executor; - }; -diff --git a/src/utils.cxx b/src/utils.cxx -index 6bb8c6c..8596808 100644 ---- a/src/utils.cxx -+++ b/src/utils.cxx -@@ -4,14 +4,20 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include "utils.h" -+#include -+#include - #include - #include -+#include - #include - #include - #include - #include - #include -+#include -+#include - #include -+#include - #include - #include - #include -@@ -27,12 +33,17 @@ - #include - #include - #include -+#include - #include -+#include - #include - #include - #include -+#include - #include -+#include - #include "shlwapi.h" -+#include "PathCch.h" - - ////////////////////////////////////////////////////////// - // String helper methods adding cxx20 features to cxx14 // -@@ -181,6 +192,17 @@ std::string lstrip(const std::string& str, const std::string& substr) { - return str.substr(substr.size(), str.size()); - } - -+/** -+ * Strips double quotes from front and back of string -+ * -+ * Returns str with no leading or trailing quotes -+ * -+ * Note: removes only one set of quotes -+ */ -+std::string stripquotes(const std::string& str) { -+ return strip(lstrip(str, "\""), "\""); -+} -+ - /** - * combines list of strings into one string joined on join_char - */ -@@ -337,7 +359,7 @@ std::string regexMatch( - if (!std::regex_match(searchDomain, match, reg, flag)) { - result_str = std::string(); - } else { -- result_str = match.str(); -+ result_str = match.str(1); - } - return result_str; - } -@@ -529,7 +551,8 @@ void replace_path_characters(char* path, size_t len) { - * null terminators. - * \param bsize the lengh of the padding to add - */ --char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { -+char* pad_path(const char* pth, DWORD str_size, char padding_char, -+ DWORD bsize) { - // If str_size > bsize we get inappropriate conversion - // from signed to unsigned - if (str_size > bsize) { -@@ -543,13 +566,26 @@ char* pad_path(const char* pth, DWORD str_size, DWORD bsize) { - padded_path[i] = pth[j]; - ++j; - } else { -- padded_path[i] = '|'; -+ padded_path[i] = padding_char; - } - } - padded_path[bsize] = '\0'; - return padded_path; - } - -+std::string escape_backslash(const std::string& path) { -+ std::string escaped; -+ escaped.reserve(path.length() * 2); -+ for (char const c : path) { -+ if (c == '\\') { -+ escaped += "\\\\"; -+ } else { -+ escaped += c; -+ } -+ } -+ return escaped; -+} -+ - /** - * Given a padded library path, return how much the path - * has been padded -@@ -591,11 +627,37 @@ std::string getSFN(const std::string& path) { - // Use "disable string parsing" prefix in case - // the path is too long - std::string const escaped = R"(\\?\)" + path; -+ // We cannot get the sfn for a path that doesn't exist -+ // if we find that the sfn we're looking for doesn't exist -+ // create a stub of the file, and allow the subsequent -+ // commands to overwrite it -+ if (!PathFileExistsA(path.c_str())) { -+ HANDLE h_file = CreateFileA(path.c_str(), GENERIC_WRITE, 0, nullptr, -+ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (h_file == INVALID_HANDLE_VALUE) { -+ debug("File " + path + -+ " does not exist, nor can it be created, unable to " -+ "compute SFN\n"); -+ CloseHandle(h_file); -+ return std::string(); -+ } -+ CloseHandle(h_file); -+ } - // Get SFN length so we can create buffer - DWORD const sfn_size = - GetShortPathNameA(escaped.c_str(), NULL, 0); //NOLINT - char* sfn = new char[sfn_size + 1]; -- GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ DWORD const res = GetShortPathNameA(escaped.c_str(), sfn, escaped.length()); -+ if (!res) { -+ -+ std::cerr << "Failed to process short name for " << path -+ << " Error: " << reportLastError() << "\n"; -+ } -+ if (!sfn && res) { -+ // buffer was too small -+ debug("buffer too small; had: " + std::to_string(sfn_size) + -+ " needed: " + std::to_string(res)); -+ } - // sfn is null terminated per win32 api - // Ensure we strip out the disable string parsing prefix - std::string s_sfn = lstrip(sfn, R"(\\?\)"); -@@ -623,6 +685,44 @@ std::string short_name(const std::string& path) { - return new_abs_out; - } - -+std::string MakePathAbsolute(const std::string& path) { -+ if (IsPathAbsolute(path)) { -+ return path; -+ } -+ // relative paths, assume they're relative to the CWD of the linker (as they have to be) -+ return join({GetCWD(), path}, "\\"); -+} -+ -+std::string CannonicalizePath(const std::string& path) { -+ std::wstring const wpath = ConvertASCIIToWide(path); -+ wchar_t canonicalized_path[PATHCCH_MAX_CCH]; -+ const size_t buffer_size = ARRAYSIZE(canonicalized_path); -+ -+ HRESULT const status = PathCchCanonicalizeEx( -+ canonicalized_path, buffer_size, wpath.c_str(), -+ PATHCCH_ALLOW_LONG_PATHS // Flags for long path support -+ ); -+ -+ if (!SUCCEEDED(status)) { -+ std::stringstream status_report; -+ status_report << "Cannot canonicalize path " + path + " error: " -+ << std::hex << status; -+ throw NameTooLongError(status_report.str().c_str()); -+ } -+ return ConvertWideToASCII(canonicalized_path); -+} -+ -+std::string EnsureValidLengthPath(const std::string& path) { -+ std::string proper_length_path = path; -+ if (path.length() > MAX_NAME_LEN) { -+ // Name is too long we need to attempt to shorten -+ std::string const short_path = short_name(path); -+ // If new, shortened path is too long, bail -+ proper_length_path = short_path; -+ } -+ return proper_length_path; -+} -+ - /** - * Mangles a string representing a path to have no path characters - * instead path characters (i.e. \\, :, etc) are replaced with -@@ -633,19 +733,10 @@ std::string short_name(const std::string& path) { - std::string mangle_name(const std::string& name) { - std::string abs_out; - std::string mangled_abs_out; -- if (IsPathAbsolute(name)) { -- abs_out = name; -- } else { -- // relative paths, assume they're relative to the CWD of the linker (as they have to be) -- abs_out = join({GetCWD(), name}, "\\"); -- } -+ abs_out = MakePathAbsolute(name); -+ abs_out = CannonicalizePath(abs_out); - // Now that we have the full path, check size -- if (abs_out.length() > MAX_NAME_LEN) { -- // Name is too long we need to attempt to shorten -- std::string const new_abs_out = short_name(abs_out); -- // If new, shortened path is too long, bail -- abs_out = new_abs_out; -- } -+ abs_out = EnsureValidLengthPath(abs_out); - char* chr_abs_out = new char[abs_out.length() + 1]; - strcpy(chr_abs_out, abs_out.c_str()); - replace_path_characters(chr_abs_out, abs_out.length()); -@@ -688,7 +779,7 @@ bool SpackInstalledLib(const std::string& lib) { - return false; - } - std::string const stripped_lib = strip_padding(lib); -- startswith(stripped_lib, prefix); -+ return startswith(stripped_lib, prefix); - } - - LibraryFinder::LibraryFinder() : search_vars{"SPACK_RELOCATE_PATH"} {} -@@ -786,13 +877,74 @@ std::string LibraryFinder::Finder(const std::string& pth, - return std::string(); - } - -+PathRelocator::PathRelocator() { -+ this->new_prefix_ = GetSpackEnv("SPACK_INSTALL_PREFIX"); -+ this->parseRelocate(); -+} -+ -+void PathRelocator::parseRelocate() { -+ const std::string relocations = GetSpackEnv("SPACK_RELOCATE_PATH"); -+ // relocations is a semi colon separated list of -+ // | separated pairs, of old_prefix|new_prefix -+ // where old prefix is either the stage or the -+ // old install root and new prefix is the dll location in the -+ // install tree or just the new install prefix -+ if (relocations.empty()) { -+ return; -+ } -+ const StrList mappings = split(relocations, ";"); -+ for (const auto& pair : mappings) { -+ const StrList old_new = split(pair, "|"); -+ const std::string& old = old_new[0]; -+ const std::string& new_ = old_new[1]; -+ this->old_new_map[old] = new_; -+ if (endswith(old, ".dll") || endswith(old, ".exe")) { -+ this->bc_ = false; -+ } -+ } -+} -+ -+std::string PathRelocator::getRelocation(std::string const& pe) { -+ if (this->bc_) { -+ return this->relocateBC(pe); -+ } -+ return this->relocateStage(pe); -+} -+ -+std::string PathRelocator::relocateBC(std::string const& pe) { -+ for (auto& root : this->old_new_map) { -+ if (startswith(pe, root.first)) { -+ std::array rel_root; -+ if (PathRelativePathToW( -+ &rel_root[0], ConvertASCIIToWide(root.first).c_str(), -+ FILE_ATTRIBUTE_DIRECTORY, ConvertASCIIToWide(pe).c_str(), -+ FILE_ATTRIBUTE_NORMAL) != 0) { -+ // we have the pe's relative root in the old -+ // prefix, slap the new prefix on it and return -+ std::string const real_rel( -+ ConvertWideToASCII(std::wstring(&rel_root[0]))); -+ return join({root.second, real_rel}, "\\"); -+ } -+ } -+ } -+ return std::string(); -+} -+ -+std::string PathRelocator::relocateStage(std::string const& pe) { -+ try { -+ std::string prefix_loc = this->old_new_map.at(pe); -+ return prefix_loc; -+ } catch (std::out_of_range& e) { -+ return std::string(); -+ } -+} -+ - namespace { - std::vector system_locations = { - "api-ms-", "ext-ms-", "ieshims", "emclient", "devicelock", - "wpax", "vcruntime", "WINDOWS", "system32", "KERNEL32", - "WS2_32", "dbghelp", "bcrypt", "ADVAPI32", "SHELL32", - "CRYPT32", "USER32", "ole32", "OLEAUTH32"}; -- - } - - bool LibraryFinder::IsSystem(const std::string& pth) { -@@ -841,9 +993,230 @@ char* findstr(char* search_str, const char* substr, size_t size) { - return nullptr; - } - -+ScopedSid FileSecurity::GetCurrentUserSid() { -+ HANDLE token_handle = nullptr; -+ if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, -+ &token_handle)) { -+ return nullptr; -+ } -+ std::unique_ptr const scoped_token( -+ token_handle, &::CloseHandle); -+ -+ DWORD buffer_size = 0; -+ ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); -+ -+ std::vector buffer(buffer_size); -+ auto* token_user = reinterpret_cast(buffer.data()); -+ -+ if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, -+ &buffer_size)) { -+ return nullptr; -+ } -+ -+ DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); -+ void* sid_copy = std::malloc(sid_len); -+ if (sid_copy) { -+ ::CopySid(sid_len, sid_copy, token_user->User.Sid); -+ return ScopedSid(sid_copy); -+ } -+ return nullptr; -+} -+ -+bool FileSecurity::HasPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid) { -+ PACL dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ ScopedLocalInfo const scoped_sd(sd_raw); -+ -+ TRUSTEE_W trustee = {nullptr}; -+ trustee.TrusteeForm = TRUSTEE_IS_SID; -+ trustee.TrusteeType = TRUSTEE_IS_USER; -+ trustee.ptstrName = static_cast(sid); -+ -+ ACCESS_MASK effective_rights = 0; -+ result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) -+ return true; -+ if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) -+ return true; -+ if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) -+ return true; -+ -+ return (effective_rights & access_mask) == access_mask; -+} -+ -+bool FileSecurity::GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd) { -+ PACL old_dacl = nullptr; -+ PSECURITY_DESCRIPTOR sd_raw = nullptr; -+ -+ DWORD result = ::GetNamedSecurityInfoW( -+ file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, -+ nullptr, &old_dacl, nullptr, &sd_raw); -+ -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ if (out_old_sd) -+ *out_old_sd = sd_raw; -+ ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); -+ -+ EXPLICIT_ACCESS_W ea = {0}; -+ ea.grfAccessPermissions = access_mask; -+ ea.grfAccessMode = GRANT_ACCESS; -+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; -+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER; -+ ea.Trustee.ptstrName = static_cast(sid); -+ -+ PACL new_dacl = nullptr; -+ result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); -+ if (result != ERROR_SUCCESS) -+ return false; -+ -+ ScopedLocalInfo const scoped_new_dacl(new_dacl); -+ result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, new_dacl, nullptr); -+ return (result == ERROR_SUCCESS); -+} -+ -+bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd) { -+ if (!sd) -+ return false; -+ BOOL present = FALSE; -+ BOOL defaulted = FALSE; -+ PACL dacl = nullptr; -+ if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || -+ !present) -+ return false; -+ -+ return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), -+ SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, -+ nullptr, nullptr, dacl, -+ nullptr) == ERROR_SUCCESS; -+} -+ -+bool FileSecurity::GetAttributes(const std::wstring& file_path, -+ DWORD* out_attr) { -+ DWORD const attr = ::GetFileAttributesW(file_path.c_str()); -+ if (attr == INVALID_FILE_ATTRIBUTES) -+ return false; -+ if (out_attr) -+ *out_attr = attr; -+ return true; -+} -+ -+bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { -+ return ::SetFileAttributesW(file_path.c_str(), attr) != 0; -+} -+ -+ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) -+ : file_path_(std::move(file_path)), -+ desired_access_(desired_access), -+ original_sd_(nullptr), -+ current_user_sid_(nullptr), -+ acl_needs_revert_(false), -+ original_attributes_(0), -+ attributes_changed_(false) { -+ -+ // We must ensure we have permissions *first* before we try to -+ // change the file attributes in Phase 2. -+ -+ current_user_sid_ = FileSecurity::GetCurrentUserSid(); -+ if (!current_user_sid_) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), "Failed to get SID"); -+ } -+ -+ // Check if we need to modify ACLs -+ if (!FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get())) { -+ if (!FileSecurity::GrantPermission(file_path_, desired_access_, -+ current_user_sid_.get(), -+ &original_sd_)) { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to grant ACL"); -+ } -+ acl_needs_revert_ = true; -+ } -+ -+ if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { -+ if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { -+ // Remove the Read-Only bit -+ DWORD const new_attributes = -+ original_attributes_ & ~FILE_ATTRIBUTE_READONLY; -+ -+ if (FileSecurity::SetAttributes(file_path_, new_attributes)) { -+ attributes_changed_ = true; -+ } else { -+ // If we fail to remove Read-Only, we might still fail to write later. -+ // We throw here to be safe and consistent. -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to remove Read-Only attribute"); -+ } -+ } -+ } else { -+ throw std::system_error(static_cast(::GetLastError()), -+ std::system_category(), -+ "Failed to get file attributes"); -+ } -+} -+ -+ScopedFileAccess::~ScopedFileAccess() { -+ // We must restore attributes *before* we revert ACLs, because reverting ACLs -+ // might remove our permission to write attributes. -+ if (attributes_changed_) { -+ // We ignore errors in destructors to prevent termination -+ FileSecurity::SetAttributes(file_path_, original_attributes_); -+ } -+ -+ if (acl_needs_revert_ && original_sd_) { -+ FileSecurity::ApplyDescriptor(file_path_, original_sd_); -+ ::LocalFree(original_sd_); -+ } -+} -+ -+bool ScopedFileAccess::IsAccessGranted() const { -+ // If we had to change anything, we assume success (constructor would throw otherwise) -+ if (acl_needs_revert_ || attributes_changed_) -+ return true; -+ -+ return FileSecurity::HasPermission(file_path_, desired_access_, -+ current_user_sid_.get()); -+} -+ - NameTooLongError::NameTooLongError(char const* const message) - : std::runtime_error(message) {} - - char const* NameTooLongError::what() const { - return exception::what(); -+} -+ -+RCCompilerFailure::RCCompilerFailure(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* RCCompilerFailure::what() const { -+ return exception::what(); -+} -+ -+FileIOError::FileIOError(char const* const message) -+ : std::runtime_error(message) {} -+ -+char const* FileIOError::what() const { -+ return exception::what(); - } -\ No newline at end of file -diff --git a/src/utils.h b/src/utils.h -index b04d929..8ded476 100644 ---- a/src/utils.h -+++ b/src/utils.h -@@ -16,6 +16,9 @@ - #include - #include - #include -+#include -+#include -+#include - - #include "version.h" - -@@ -52,7 +55,8 @@ enum ExitConditions { - LIB_REMOVE_FAILURE, - NORMALIZE_NAME_FAILURE, - COFF_PARSE_FAILURE, -- FILE_RENAME_FAILURE -+ FILE_RENAME_FAILURE, -+ CANNOT_OPEN_FILE_FAILURE - }; - - typedef std::vector StrList; -@@ -95,6 +99,9 @@ std::string strip(const std::string& s, const std::string& substr); - //Strips substr of LHS of the larger string - std::string lstrip(const std::string& s, const std::string& substr); - -+//Strips off leading and trailing quotes -+std::string stripquotes(const std::string& str); -+ - // Joins vector of strings by join character - std::string join(const StrList& args, const std::string& join_char = " "); - -@@ -186,9 +193,14 @@ std::string short_name(const std::string& path); - - std::string mangle_name(const std::string& name); - -+std::string CannonicalizePath(const std::string& path); -+ - int get_padding_length(const std::string& name); - --char* pad_path(const char* pth, DWORD str_size, DWORD bsize = MAX_NAME_LEN); -+char* pad_path(const char* pth, DWORD str_size, char padding_char = '|', -+ DWORD bsize = MAX_NAME_LEN); -+ -+std::string escape_backslash(const std::string& path); - - void replace_path_characters(char* path, size_t len); - -@@ -196,6 +208,10 @@ void replace_special_characters(char* mangled, size_t len); - - bool SpackInstalledLib(const std::string& lib); - -+std::string MakePathAbsolute(const std::string& path); -+ -+std::string EnsureValidLengthPath(const std::string& path); -+ - // File and File handle helpers // - /** - * @brief Returns boolean indicating whether -@@ -218,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); - // System Helpers // - std::string reportLastError(); - -+struct LocalFreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ ::LocalFree(p); -+ } -+}; -+ -+// Custom deleter for Standard C pointers. -+struct FreeDeleter { -+ void operator()(void* p) const { -+ if (p) -+ std::free(p); -+ } -+}; -+ - // Data helpers // - - // Converts big endian data to little endian form -@@ -262,6 +293,71 @@ class LibraryFinder { - void EvalSearchPaths(); - }; - -+class PathRelocator { -+ private: -+ bool bc_; -+ std::string new_prefix_; -+ std::map old_new_map; -+ std::string relocateBC(std::string const& pe); -+ std::string relocateStage(std::string const& pe); -+ void parseRelocate(); -+ -+ public: -+ PathRelocator(); -+ std::string getRelocation(std::string const& pe); -+}; -+ -+using ScopedLocalInfo = std::unique_ptr; -+ -+using ScopedSid = std::unique_ptr; -+ -+class FileSecurity { -+ public: -+ FileSecurity() = delete; -+ -+ static ScopedSid GetCurrentUserSid(); -+ -+ static bool HasPermission(const std::wstring& file_path, DWORD access_mask, -+ PSID sid); -+ -+ static bool GrantPermission(const std::wstring& file_path, -+ DWORD access_mask, PSID sid, -+ PSECURITY_DESCRIPTOR* out_old_sd); -+ -+ static bool ApplyDescriptor(const std::wstring& file_path, -+ PSECURITY_DESCRIPTOR sd); -+ -+ // Retrieves file attributes (e.g., ReadOnly, Hidden). -+ // Returns false if the file cannot be accessed. -+ static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); -+ -+ // Sets file attributes. -+ // Returns false if the operation fails. -+ static bool SetAttributes(const std::wstring& file_path, DWORD attr); -+}; -+ -+class ScopedFileAccess { -+ public: -+ explicit ScopedFileAccess(std::wstring file_path, -+ DWORD desired_access = GENERIC_WRITE); -+ ~ScopedFileAccess(); -+ -+ bool IsAccessGranted() const; -+ -+ private: -+ std::wstring file_path_; -+ DWORD desired_access_; -+ -+ // ACL State -+ PSECURITY_DESCRIPTOR original_sd_; -+ ScopedSid current_user_sid_; -+ bool acl_needs_revert_; -+ -+ // Attribute State -+ DWORD original_attributes_; -+ bool attributes_changed_; -+}; -+ - const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; - - const std::map path_to_special_characters{{'\\', '|'}, -@@ -274,4 +370,16 @@ class NameTooLongError : public std::runtime_error { - virtual char const* what() const; - }; - -+class RCCompilerFailure : public std::runtime_error { -+ public: -+ RCCompilerFailure(char const* const message); -+ virtual char const* what() const; -+}; -+ -+class FileIOError : public std::runtime_error { -+ public: -+ FileIOError(char const* const message); -+ virtual char const* what() const; -+}; -+ - static bool DEBUG = false; -diff --git a/src/winrpath.cxx b/src/winrpath.cxx -index f98e063..24198a9 100644 ---- a/src/winrpath.cxx -+++ b/src/winrpath.cxx -@@ -4,7 +4,6 @@ - * SPDX-License-Identifier: (Apache-2.0 OR MIT) - */ - #include --#include - #include // NOLINT - #include "winrpath.h" - #include -@@ -23,6 +22,7 @@ - #include - #include - #include -+#include - #include - - /* -@@ -37,21 +37,8 @@ - * \param name The dll name to check for sigils or special path characters - * - */ --bool LibRename::SpackCheckForDll(const std::string& dll_path) const { -- if (this->deploy) { -- return hasPathCharacters(dll_path); -- } -- // First check for the case we're relocating out of a buildcache -- bool reloc_spack = false; -- if (!(dll_path.find("") == std::string::npos) || -- !(dll_path.find("") == std::string::npos)) { -- reloc_spack = true; -- } -- // If not, maybe we're just relocating a binary on the same system -- if (!reloc_spack) { -- reloc_spack = hasPathCharacters(dll_path); -- } -- return reloc_spack; -+bool LibRename::SpackCheckForDll(const std::string& dll_path) { -+ return hasPathCharacters(dll_path); - } - - /* -@@ -66,58 +53,36 @@ bool LibRename::SpackCheckForDll(const std::string& dll_path) const { - * the dll name found at `name_loc` to the absolute path of - * - */ --bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) const { -- if (this->deploy) { -- int const padding_len = get_padding_length(dll_path); -- if (padding_len < MIN_PADDING_THRESHOLD) { -- // path is too long to mark as a Spack path -- // use shorter sigil -- char short_sigil[] = ""; -- // use _snprintf as it does not null terminate and we're writing into the middle -- // of a null terminated string we want to later read from properly -- _snprintf(name_loc, sizeof(short_sigil) - 1, "%s", short_sigil); -- } else { -- char long_sigil[] = ""; -- // See _snprintf comment above for use context -- _snprintf(name_loc, sizeof(long_sigil) - 1, "%s", long_sigil); -- } -- } else { -- if (SpackInstalledLib(dll_path)) { -- return true; -- } -- std::string const file_name = basename(dll_path); -- if (file_name.empty()) { -- std::cerr << "Unable to extract filename from dll for relocation" -- << "\n"; -- return false; -- } -- LibraryFinder lib_finder; -- std::string new_library_loc = -- lib_finder.FindLibrary(file_name, dll_path); -- if (new_library_loc.empty()) { -- std::cerr << "Unable to find library " << file_name << " from " -- << dll_path << " for relocation" << "\n"; -- return false; -- } -- if (new_library_loc.length() > MAX_NAME_LEN) { -- try { -- new_library_loc = short_name(new_library_loc); -- } catch (NameTooLongError& e) { -- return false; -- } -- } -- char* new_lib_pth = -- pad_path(new_library_loc.c_str(), -- static_cast(new_library_loc.size())); -- if (!new_lib_pth) { -- return false; -- } -- replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+bool LibRename::RenameDll(char* name_loc, const std::string& dll_path) { -+ if (SpackInstalledLib(dll_path)) { -+ return true; -+ } -+ PathRelocator relocator; -+ std::string new_loc = relocator.getRelocation(dll_path); -+ if (new_loc.empty()) { -+ std::cerr << "Cannot find relocation mapping for library " << dll_path -+ << "\n"; -+ return false; -+ } -+ try { -+ new_loc = -+ EnsureValidLengthPath(CannonicalizePath(MakePathAbsolute(new_loc))); -+ } catch (NameTooLongError& e) { -+ std::cerr << "Cannot relocate path " << new_loc -+ << "it is too long to be relocated safely.\n"; -+ return false; -+ } - -- // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -- // size differences w.r.t the path to the new library -- snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); -+ char* new_lib_pth = -+ pad_path(new_loc.c_str(), static_cast(new_loc.size())); -+ if (!new_lib_pth) { -+ return false; - } -+ replace_special_characters(new_lib_pth, MAX_NAME_LEN); -+ -+ // c_str returns a proper (i.e. null terminated) value, so we dont need to worry about -+ // size differences w.r.t the path to the new library -+ snprintf(name_loc, MAX_NAME_LEN + 1, "%s", new_lib_pth); - return true; - } - -@@ -206,8 +171,8 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - import_table_offset + - (import_image_descriptor->Name - rva_import_directory); - std::string const str_dll_name = std::string(imported_dll); -- if (this->SpackCheckForDll(str_dll_name)) { -- if (!this->RenameDll(imported_dll, str_dll_name)) { -+ if (LibRename::SpackCheckForDll(str_dll_name)) { -+ if (!LibRename::RenameDll(imported_dll, str_dll_name)) { - std::cerr << "Unable to relocate DLL reference: " - << str_dll_name << "\n"; - return false; -@@ -244,16 +209,18 @@ bool LibRename::FindDllAndRename(HANDLE& pe_in) { - * \param replace a flag indicating if we're replacing the renamed import lib or making a copy with absolute dll names - * \param report a flag indicating if we should be reporting the contents of the PE/COFF file we're parsing to stdout - */ --LibRename::LibRename(std::string p_exe, bool full, bool deploy, bool replace) -- : replace(replace), full(full), pe(std::move(p_exe)), deploy(deploy) {} -+LibRename::LibRename(std::string p_exe, bool full, bool replace) -+ : replace(replace), full(full), pe(std::move(p_exe)) { -+ this->pe = MakePathAbsolute(this->pe); -+} - - LibRename::LibRename(std::string p_exe, std::string coff, bool full, -- bool deploy, bool replace) -+ bool replace) - : replace(replace), - full(full), - pe(std::move(p_exe)), -- deploy(deploy), - coff(std::move(coff)) { -+ this->pe = MakePathAbsolute(this->pe); - std::string const coff_path = stem(this->coff); - this->tmp_def_file = coff_path + "-tmp.def"; - this->def_file = coff_path + ".def"; -@@ -322,7 +289,9 @@ bool LibRename::ComputeDefFile() { - npos) { // Skip header in export block if still present - break; - } -- output_file << " " << regexReplace(line, R"(\s+)", "") << '\n'; -+ output_file << " " -+ << regexMatch(line, R"(^.*?(\S+)(?:\s+\(.*\))?\s*$)") -+ << '\n'; - } - input_file.close(); - output_file.close(); -@@ -357,7 +326,7 @@ bool LibRename::ExecuteRename() { - // exes - // We do not bother with defs for things that don't have - // import libraries -- if (!this->deploy && !this->coff.empty()) { -+ if (!this->coff.empty()) { - // Extract DLL - if (!this->ComputeDefFile()) { - debug("Failed to compute def file"); -@@ -439,15 +408,23 @@ bool LibRename::ExecutePERename() { - std::cerr << e.what() << "\n"; - return false; - } -- HANDLE pe_handle = CreateFileW( -- pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, -- nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -- if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -- std::cerr << "Unable to acquire file handle to " << pe_path.c_str() -- << ": " << reportLastError() << "\n"; -+ try { -+ ScopedFileAccess const obtain_write(pe_path, GENERIC_ALL); -+ HANDLE pe_handle = CreateFileW( -+ pe_path.c_str(), (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_WRITE, -+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); -+ if (!pe_handle || pe_handle == INVALID_HANDLE_VALUE) { -+ std::cerr << "Unable to acquire file handle to " -+ << ConvertWideToASCII(pe_path) << ": " -+ << reportLastError() << "\n"; -+ return false; -+ } -+ return LibRename::FindDllAndRename(pe_handle); -+ } catch (const std::system_error& e) { -+ std::cerr << "Could not obtain write access: " << e.what() -+ << " (Error Code: " << e.code().value() << ")" << '\n'; - return false; - } -- return this->FindDllAndRename(pe_handle); - } - - /* Construct the line needed to produce a new import library -diff --git a/src/winrpath.h b/src/winrpath.h -index e166465..16aeff6 100644 ---- a/src/winrpath.h -+++ b/src/winrpath.h -@@ -31,9 +31,8 @@ - - class LibRename { - public: -- LibRename(std::string p_exe, std::string coff, bool full, bool deploy, -- bool replace); -- LibRename(std::string p_exe, bool full, bool deploy, bool replace); -+ LibRename(std::string p_exe, std::string coff, bool full, bool replace); -+ LibRename(std::string p_exe, bool full, bool replace); - bool ExecuteRename(); - bool ExecuteLibRename(); - bool ExecutePERename(); -@@ -42,9 +41,9 @@ class LibRename { - std::string ComputeDefLine(); - - private: -- bool FindDllAndRename(HANDLE& pe_in); -- bool SpackCheckForDll(const std::string& dll_path) const; -- bool RenameDll(char* name_loc, const std::string& dll_path) const; -+ static bool FindDllAndRename(HANDLE& pe_in); -+ static bool SpackCheckForDll(const std::string& dll_path) ; -+ static bool RenameDll(char* name_loc, const std::string& dll_path) ; - ExecuteCommand def_executor; - ExecuteCommand lib_executor; - std::string pe; -@@ -53,6 +52,5 @@ class LibRename { - std::string def_file; - std::string tmp_def_file; - bool full; -- bool deploy; - bool replace; - }; -diff --git a/test/setup_and_drive_test.bat b/test/setup_and_drive_test.bat -index 88285fd..2ae5e83 100644 ---- a/test/setup_and_drive_test.bat -+++ b/test/setup_and_drive_test.bat -@@ -17,6 +17,5 @@ SET SPACK_DEBUG_LOG_ID=TEST - SET SPACK_SHORT_SPEC=test%msvc - SET SPACK_SYSTEM_DIRS=%PATH% - SET SPACK_MANAGED_DIRS=%CD%\tmp --SET SPACK_RELOCATE_PATH=%CD%\tmp - - nmake test -\ No newline at end of file From ca2a08cfad2ca24d297dedfeb098099866e6019e Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 14:37:07 -0400 Subject: [PATCH 24/81] restore package Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 7445b5ab15b..67704dcba9d 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -6,6 +6,7 @@ import sys from spack_repo.builtin.build_systems.nmake import NMakePackage, NMakeBuilder +from spack_repo.builtin.build_systems.generic import Package from spack.package import * From 5330a8f91cb410102b0578893e1cae2cc4b84721 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 14:41:19 -0400 Subject: [PATCH 25/81] style Signed-off-by: John Parent --- .../builtin/packages/compiler_wrapper/package.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 67704dcba9d..d5029730afa 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -5,8 +5,8 @@ import shutil import sys -from spack_repo.builtin.build_systems.nmake import NMakePackage, NMakeBuilder from spack_repo.builtin.build_systems.generic import Package +from spack_repo.builtin.build_systems.nmake import NMakeBuilder, NMakePackage from spack.package import * @@ -97,7 +97,7 @@ class EnvironmentSetup: def setup_dependent_build_environment( self, env: EnvironmentModifications, dependent_spec: Spec ) -> None: - + _var_list = [] if dependent_spec.has_virtual_dependency("c"): _var_list.append(("c", "cc", "CC", "SPACK_CC")) @@ -193,7 +193,6 @@ def setup_dependent_build_environment( class GenericBuilder(GenericBuilder, EnvironmentSetup): - def install(self, pkg, spec, prefix): cc_script = pathlib.Path(self.stage.source_path) / "cc.sh" bin_dir = pkg.bin_dir() @@ -277,7 +276,6 @@ def install(self, pkg, spec, prefix): fj_dir.mkdir(exist_ok=True) (fj_dir / "FCC").symlink_to(installed_script) - class NMakeBuilder(NMakeBuilder, EnvironmentSetup): install_targets = ["install"] @@ -294,7 +292,6 @@ def install(self, pkg, spec, prefix): for name in ("link", "ftn", "fc", "f95", "f90", "f77", "cpp", "c99", "c89", "c++"): (bin_dir / name).symlink_to(bin_dir / "cl.exe") - for subdir, name in ( ("case-insensitive", "CC.exe"), ("intel", "ifort.exe"), @@ -303,6 +300,3 @@ def install(self, pkg, spec, prefix): ): (bin_dir / subdir).mkdir(exist_ok=True) (bin_dir / subdir / name).symlink_to(bin_dir / "cl.exe") - - - From f9c9153e064be7536a515e9f6744b46468801251 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 14:59:08 -0400 Subject: [PATCH 26/81] Proper msvc depend Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index d5029730afa..b4b47127764 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -45,7 +45,7 @@ class CompilerWrapper(Package, NMakePackage): # FIXME (compiler as nodes): use a different tag, since this is only to exclude # this node from auto-generated rules tags = ["runtime"] - depends_on("msvc", type="build") + depends_on("msvc", when="platform=windows") maintainers("haampie") From 8b85fd148df55a585e9dd5e19be019577a953bef Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 14:59:52 -0400 Subject: [PATCH 27/81] Add myself as maintainer for windows side Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index b4b47127764..c736785a7cb 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -47,7 +47,7 @@ class CompilerWrapper(Package, NMakePackage): tags = ["runtime"] depends_on("msvc", when="platform=windows") - maintainers("haampie") + maintainers("haampie", "johnwparent") license("Apache-2.0 OR MIT") From 65b11392795607b2d535a1e8f0bd5247e0153fb9 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 15:03:14 -0400 Subject: [PATCH 28/81] style Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msvc/package.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index eff8b7ec9c3..8870f8b8cb1 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -270,9 +270,9 @@ def platform_toolset_ver(self): @property def ld(self): assert self.spec.concrete, "cannot retrieve C++ linker, spec is not concrete" - assert ( - self.spec.external - ), "MSVC is external only, please report this bug to the Spack maintainers" + assert self.spec.external, ( + "MSVC is external only, please report this bug to the Spack maintainers" + ) return self.spec.extra_attributes.get("compilers", {}).get("ld", None) From 3367fba72e36f63f301ffbccbef7642d3b9e729a Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 15:16:30 -0400 Subject: [PATCH 29/81] small reorg Signed-off-by: John Parent --- .../spack_repo/builtin/packages/compiler_wrapper/package.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index c736785a7cb..5cff7c091f8 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -45,7 +45,6 @@ class CompilerWrapper(Package, NMakePackage): # FIXME (compiler as nodes): use a different tag, since this is only to exclude # this node from auto-generated rules tags = ["runtime"] - depends_on("msvc", when="platform=windows") maintainers("haampie", "johnwparent") @@ -60,6 +59,10 @@ class CompilerWrapper(Package, NMakePackage): else: version("develop", branch="main") + + depends_on("msvc", when="platform=windows") + + def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get # their way to the default view From 9bfed84acf2ea298c74e2a90646b0a7d4b873dbb Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 15:16:58 -0400 Subject: [PATCH 30/81] give msvc a stub version to make audit check happy Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msvc/package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index 8870f8b8cb1..63839d1ded6 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -53,6 +53,8 @@ def install(self, spec, prefix): "fortran": "oneapi\\ifx.exe", } + version("19.16.27054") + provides("c", "cxx", "fortran") requires("platform=windows", msg="MSVC is only supported on Windows") From d70adf01c68fab5eb8c4a7bb6d15a49d04479f93 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 15:19:09 -0400 Subject: [PATCH 31/81] style Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 5cff7c091f8..3758ea06634 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,10 +59,8 @@ class CompilerWrapper(Package, NMakePackage): else: version("develop", branch="main") - depends_on("msvc", when="platform=windows") - def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get # their way to the default view From d702a09bd90079afac316b48fdb5e92571df175e Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 5 May 2026 17:18:13 -0400 Subject: [PATCH 32/81] REVERT ME: point gl CI at real core wrapper changes Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index e9328370248..36c0adb92f4 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=84cb7ff86b42e7fa45476750b6729144c58f70dc +SPACK_CHECKOUT_VERSION=22627ef8e6e2a804aa8d57a67e5a191fd58266dc SPACK_CHECKOUT_REPO=spack/spack From 29c11c983ec84dd8c1a8ac625fd472ef66ba15f5 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 6 May 2026 12:46:55 -0400 Subject: [PATCH 33/81] Fix LD for msvc's without extra attr Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msvc/package.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index 63839d1ded6..d9f8155fff0 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -275,8 +275,10 @@ def ld(self): assert self.spec.external, ( "MSVC is external only, please report this bug to the Spack maintainers" ) - - return self.spec.extra_attributes.get("compilers", {}).get("ld", None) + ld = self.spec.extra_attributes.get("compilers", {}).get("ld", None) + if not ld: + ld = os.path.join(os.path.dirname(self.cc), "link.exe") + return ld class CmdCall: From e7cfbc309c21472bb7008c3574923d1324355199 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 8 May 2026 13:06:04 -0400 Subject: [PATCH 34/81] Try to disable a spec to see if anything changes Signed-off-by: John Parent --- stacks/windows-vis/spack.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacks/windows-vis/spack.yaml b/stacks/windows-vis/spack.yaml index fc6519cd863..a0dca7dedbb 100644 --- a/stacks/windows-vis/spack.yaml +++ b/stacks/windows-vis/spack.yaml @@ -9,7 +9,7 @@ spack: - ../../.ci/gitlab/ specs: - - "vtk@9: +mpi" + # - "vtk@9: +mpi" - "boost" - "paraview@:5.13.1+mpi+qt" - "py-numpy ^netlib-lapack" From 83d178a1a18fc10beeed9702921af6a098501316 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 8 May 2026 16:02:55 -0400 Subject: [PATCH 35/81] Try more msvc versions? Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msvc/package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index d9f8155fff0..e2fcf5ad083 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -53,7 +53,9 @@ def install(self, spec, prefix): "fortran": "oneapi\\ifx.exe", } + version("19.39.33523") version("19.16.27054") + version("19.16.27051") provides("c", "cxx", "fortran") requires("platform=windows", msg="MSVC is only supported on Windows") From 85e205395ff8dccdd1c6b80e5f129825a37666a6 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 8 May 2026 16:40:51 -0400 Subject: [PATCH 36/81] real basic ci Signed-off-by: John Parent --- stacks/windows-vis/spack.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stacks/windows-vis/spack.yaml b/stacks/windows-vis/spack.yaml index a0dca7dedbb..dbccd5639aa 100644 --- a/stacks/windows-vis/spack.yaml +++ b/stacks/windows-vis/spack.yaml @@ -10,9 +10,10 @@ spack: specs: # - "vtk@9: +mpi" - - "boost" - - "paraview@:5.13.1+mpi+qt" - - "py-numpy ^netlib-lapack" + # - "boost" + # - "paraview@:5.13.1+mpi+qt" + # - "py-numpy ^netlib-lapack" + - "zlib" cdash: build-group: Windows Visualization (Kitware) From b0f49d05832a01530e4fa87fb4c6c92f979a2580 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 11 May 2026 17:40:17 -0400 Subject: [PATCH 37/81] revert me: bump core commit sha Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index 36c0adb92f4..6f316269a8c 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=22627ef8e6e2a804aa8d57a67e5a191fd58266dc +SPACK_CHECKOUT_VERSION=8e7467e52fab2fd9ad4a8bf5de84c7ea7e9a4cef SPACK_CHECKOUT_REPO=spack/spack From 21a5c6dd9b6dcfd516e93ba97071b6f67188a9ce Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 11 May 2026 17:44:10 -0400 Subject: [PATCH 38/81] Revert me: another ci bump Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index 6f316269a8c..e4b71b19e77 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=8e7467e52fab2fd9ad4a8bf5de84c7ea7e9a4cef +SPACK_CHECKOUT_VERSION=ee09df9f0a6fe92fd630663d92b6d8636dad5726 SPACK_CHECKOUT_REPO=spack/spack From 546c6018ebfd9e846b5da4b060a633186e6ea5b5 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 11 May 2026 18:01:47 -0400 Subject: [PATCH 39/81] another bump Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index e4b71b19e77..db95d57d0c3 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=ee09df9f0a6fe92fd630663d92b6d8636dad5726 +SPACK_CHECKOUT_VERSION=6e4a35bd9c8715e6231043cf712f2363203753ed SPACK_CHECKOUT_REPO=spack/spack From 4359a98f51fcc328820a50155cf83cad490136ba Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 13 May 2026 16:04:01 -0400 Subject: [PATCH 40/81] Try build only dep on msvc Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 3758ea06634..094468b7e63 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): else: version("develop", branch="main") - depends_on("msvc", when="platform=windows") + depends_on("msvc", when="platform=windows", type="build") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From 337d3eac7ca7734e64443e17ca2b5e406a0aefca Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 13 May 2026 16:15:31 -0400 Subject: [PATCH 41/81] no msvc at all Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 094468b7e63..3e8ad12a761 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): else: version("develop", branch="main") - depends_on("msvc", when="platform=windows", type="build") + # depends_on("msvc", when="platform=windows", type="build") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From f517abaf6696c835a3db09fddc6e0be130fce056 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 13 May 2026 15:54:17 -0400 Subject: [PATCH 42/81] conc the compiler Signed-off-by: John Parent --- stacks/windows-vis/spack.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stacks/windows-vis/spack.yaml b/stacks/windows-vis/spack.yaml index dbccd5639aa..8852acd7e6a 100644 --- a/stacks/windows-vis/spack.yaml +++ b/stacks/windows-vis/spack.yaml @@ -13,7 +13,8 @@ spack: # - "boost" # - "paraview@:5.13.1+mpi+qt" # - "py-numpy ^netlib-lapack" - - "zlib" + # - "zlib" + - "msvc" cdash: build-group: Windows Visualization (Kitware) From fa7994220e86ebbf00100627e97509723e9f8341 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 14 May 2026 18:12:41 -0400 Subject: [PATCH 43/81] solve Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 1 + stacks/windows-vis/spack.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index 748612ea3b5..fb59c34acb5 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -114,6 +114,7 @@ default: - spack config add -f "${SPACK_CI_CONFIG_ROOT}/mirrors.yaml" - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir" - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}" + - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" - spack -v --color=always diff --git a/stacks/windows-vis/spack.yaml b/stacks/windows-vis/spack.yaml index 8852acd7e6a..01ce5f76ff4 100644 --- a/stacks/windows-vis/spack.yaml +++ b/stacks/windows-vis/spack.yaml @@ -13,8 +13,8 @@ spack: # - "boost" # - "paraview@:5.13.1+mpi+qt" # - "py-numpy ^netlib-lapack" - # - "zlib" - - "msvc" + - "zlib" + # - "msvc" cdash: build-group: Windows Visualization (Kitware) From 43d6f3c6436154e12cfa2557837962e73f6cb649 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 20 May 2026 11:20:14 -0400 Subject: [PATCH 44/81] Still capture config Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index fb59c34acb5..a29d2b9f34c 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -114,9 +114,9 @@ default: - spack config add -f "${SPACK_CI_CONFIG_ROOT}/mirrors.yaml" - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir" - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}" - - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" + - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack -v --color=always ci generate --check-index-only -j ${SPACK_CONCRETIZE_JOBS} --forward-variable SPACK_CHECKOUT_VERSION From 36564f1266bf415f77434f720d47fe69a5379df1 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 20 May 2026 11:22:24 -0400 Subject: [PATCH 45/81] big ole sleep Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index a29d2b9f34c..0c066d1e0d5 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -117,6 +117,7 @@ default: - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" + - spack python -c "import time; time.sleep(21600)" - spack -v --color=always ci generate --check-index-only -j ${SPACK_CONCRETIZE_JOBS} --forward-variable SPACK_CHECKOUT_VERSION From ccce4b2cf98b58dbcc6b47e90a3aa2529e25ae60 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 20 May 2026 11:23:06 -0400 Subject: [PATCH 46/81] move sleep Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index 0c066d1e0d5..cc41e8026fa 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -116,8 +116,8 @@ default: - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}" - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" - - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack python -c "import time; time.sleep(21600)" + - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack -v --color=always ci generate --check-index-only -j ${SPACK_CONCRETIZE_JOBS} --forward-variable SPACK_CHECKOUT_VERSION From 9871f853598ddaeb85187c98c31b1eb3c88cb42e Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 20 May 2026 11:53:24 -0400 Subject: [PATCH 47/81] rm whitespace Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 1 - 1 file changed, 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 3e8ad12a761..0806378e1a7 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -36,7 +36,6 @@ class CompilerWrapper(Package, NMakePackage): url_win = "https://github.com/spack/msvc-wrapper/archive/refs/tags/v0.1.0.tar.gz" git_win = "https://github.com/spack/msvc-wrapper.git" - homepage = homepage_win if IS_WINDOWS else homepage_nix url = url_win if IS_WINDOWS else url_nix if IS_WINDOWS: From 99850dc145ad759fd61cd9e36663ee9352bf72cb Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 21 May 2026 15:24:25 -0400 Subject: [PATCH 48/81] try git version Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 0806378e1a7..b47b9825cc9 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -56,7 +56,8 @@ class CompilerWrapper(Package, NMakePackage): version("1.1.0", sha256="a07b35081d14b0729090bc1e5790a5dda2d5b997e064c62da39a1224ee249b2a") version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: - version("develop", branch="main") + # version("develop", branch="main") + version("0.1.2", commit="8faa607813f3a9997d5549ba79f571649ab93913") # depends_on("msvc", when="platform=windows", type="build") From ade9c03773bde2fe3fc4bbfc0a658230a203ad0b Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 21 May 2026 15:32:06 -0400 Subject: [PATCH 49/81] r/nosleep Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index cc41e8026fa..a29d2b9f34c 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -116,7 +116,6 @@ default: - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}" - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" - - spack python -c "import time; time.sleep(21600)" - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack -v --color=always ci generate --check-index-only -j ${SPACK_CONCRETIZE_JOBS} From 241fed6620bcde159ec7c49f5d052b9c594626c0 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 21 May 2026 16:59:40 -0400 Subject: [PATCH 50/81] restore msvc dep Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index b47b9825cc9..c52e2ccce6b 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): # version("develop", branch="main") version("0.1.2", commit="8faa607813f3a9997d5549ba79f571649ab93913") - # depends_on("msvc", when="platform=windows", type="build") + depends_on("msvc", when="platform=windows", type="build") def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From df67a4bbb115fd1cf29d4816738e78729032850d Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 22 May 2026 12:10:42 -0400 Subject: [PATCH 51/81] new commit sha Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index c52e2ccce6b..2d5537ce536 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("0.1.2", commit="8faa607813f3a9997d5549ba79f571649ab93913") + version("0.1.2", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") depends_on("msvc", when="platform=windows", type="build") From fcc6f5a5d3ae6cff926b4ce7a9f6f0078c79619d Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 22 May 2026 17:54:41 -0400 Subject: [PATCH 52/81] mimic old version for now to trick bootstrap Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 2d5537ce536..a8b36f244e9 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("0.1.2", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") + version("0.1.0", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") depends_on("msvc", when="platform=windows", type="build") From fee6e759bc79a9a738e3f181aa827e5177529778 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 22 May 2026 18:27:51 -0400 Subject: [PATCH 53/81] try 1.0? Not sure 0.1.0 is coming from Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index a8b36f244e9..39a3a1d994a 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("0.1.0", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") + version("1.0", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") depends_on("msvc", when="platform=windows", type="build") From 56946c2d3e851568ca62fcadcbb92e4eb1cffd31 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 12:31:03 -0400 Subject: [PATCH 54/81] Bump core spack sha to capture bootstrap wrapper spec version update Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index db95d57d0c3..7681ad92323 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=6e4a35bd9c8715e6231043cf712f2363203753ed +SPACK_CHECKOUT_VERSION=33b222398d3aba647fd0bbfd28162c349f7b1159 SPACK_CHECKOUT_REPO=spack/spack From 0461734d71483c182d7d2879bb5fd699c57b6f19 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 15:12:19 -0400 Subject: [PATCH 55/81] support paths with spaces Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 39a3a1d994a..4ad776b0d18 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="e2c0efecb3146ad87d8b8532af5c28e462bec113") + version("1.0", commit="37b799fdc7e3e132d11a94a4ca896ca992ca24f7") depends_on("msvc", when="platform=windows", type="build") From 3e15c698087fbde358eabe5b8e13e470be489b6b Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 15:31:38 -0400 Subject: [PATCH 56/81] quick rebase Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 4ad776b0d18..29a5c48d1ca 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="37b799fdc7e3e132d11a94a4ca896ca992ca24f7") + version("1.0", commit="f5a25f8712b111bae3eab9e54cd892045268cd0e") depends_on("msvc", when="platform=windows", type="build") From 1af08046ce2cc32df0e70807abf904f87dac5619 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 15:50:10 -0400 Subject: [PATCH 57/81] quote prefix Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 29a5c48d1ca..b61ce9fb6a8 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -285,7 +285,7 @@ class NMakeBuilder(NMakeBuilder, EnvironmentSetup): def install(self, pkg, spec, prefix): bin_dir = pkg.bin_dir() opts = self.std_nmake_args - opts.append(self.define("PREFIX", str(bin_dir))) + opts.append(self.define("PREFIX", f'"{str(bin_dir)}"')) with working_dir(self.build_directory): nmake(*opts, *self.install_targets, ignore_quotes=self.ignore_quotes) From f5531e7f9b696b7f9ab94ef1c9c1d7d35660b1e6 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 16:40:31 -0400 Subject: [PATCH 58/81] Try to fix install from nmake side Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index b61ce9fb6a8..e80ab25ad08 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="f5a25f8712b111bae3eab9e54cd892045268cd0e") + version("1.0", commit="69b19d84419b944fa2cd3077333fc9c9817dc1be") depends_on("msvc", when="platform=windows", type="build") @@ -285,7 +285,7 @@ class NMakeBuilder(NMakeBuilder, EnvironmentSetup): def install(self, pkg, spec, prefix): bin_dir = pkg.bin_dir() opts = self.std_nmake_args - opts.append(self.define("PREFIX", f'"{str(bin_dir)}"')) + opts.append(self.define("PREFIX", str(bin_dir))) with working_dir(self.build_directory): nmake(*opts, *self.install_targets, ignore_quotes=self.ignore_quotes) From b8f6c32216b7305a483d551196fcdfb4547822cd Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 17:41:37 -0400 Subject: [PATCH 59/81] bump core again Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index 7681ad92323..32c96622d74 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=33b222398d3aba647fd0bbfd28162c349f7b1159 +SPACK_CHECKOUT_VERSION=1d2b11a52d581a540a47e2efbc0d71838e6e553f SPACK_CHECKOUT_REPO=spack/spack From 6f592a00de83eaaeeed384aed5e4de77c3cae947 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 26 May 2026 18:48:22 -0400 Subject: [PATCH 60/81] boneheaded upstream bug fix commit bump Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index e80ab25ad08..3f179c7c76d 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="69b19d84419b944fa2cd3077333fc9c9817dc1be") + version("1.0", commit="38cec9fb6c71e018e9cf416f938fb639a95fc377") depends_on("msvc", when="platform=windows", type="build") From d14f3d3da66baced2773acdcc942a72a842fdc7f Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 27 May 2026 09:15:31 -0400 Subject: [PATCH 61/81] Version bump for better upstream path with space support Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 3f179c7c76d..e97ba111f63 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="38cec9fb6c71e018e9cf416f938fb639a95fc377") + version("1.0", commit="1220713c8ca38d1ef745fac5c2b77ff678f71c53") depends_on("msvc", when="platform=windows", type="build") From 720657ec7ca8ccd59bf2a61f4b75d36ae0d5104e Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 27 May 2026 10:16:06 -0400 Subject: [PATCH 62/81] real vis stack Signed-off-by: John Parent --- stacks/windows-vis/spack.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/stacks/windows-vis/spack.yaml b/stacks/windows-vis/spack.yaml index 01ce5f76ff4..fc6519cd863 100644 --- a/stacks/windows-vis/spack.yaml +++ b/stacks/windows-vis/spack.yaml @@ -9,12 +9,10 @@ spack: - ../../.ci/gitlab/ specs: - # - "vtk@9: +mpi" - # - "boost" - # - "paraview@:5.13.1+mpi+qt" - # - "py-numpy ^netlib-lapack" - - "zlib" - # - "msvc" + - "vtk@9: +mpi" + - "boost" + - "paraview@:5.13.1+mpi+qt" + - "py-numpy ^netlib-lapack" cdash: build-group: Windows Visualization (Kitware) From ce443689bc20bd6d3956cfd97e683dde13e1f447 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 27 May 2026 11:37:30 -0400 Subject: [PATCH 63/81] No solve, artifact too large Signed-off-by: John Parent --- .ci/gitlab/.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/gitlab/.gitlab-ci.yml b/.ci/gitlab/.gitlab-ci.yml index a29d2b9f34c..67de4ae21f8 100644 --- a/.ci/gitlab/.gitlab-ci.yml +++ b/.ci/gitlab/.gitlab-ci.yml @@ -116,7 +116,7 @@ default: - mkdir "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}" - spack config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/spack.yaml.blame" - - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" + # - spack solve --show all > "${CI_PROJECT_DIR}/jobs_scratch_dir/${SPACK_CI_STACK_NAME}/solve.out" - spack -v --color=always ci generate --check-index-only -j ${SPACK_CONCRETIZE_JOBS} --forward-variable SPACK_CHECKOUT_VERSION From 8cad3d64214ae73b28dc29861aa482a1d58e28c9 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 27 May 2026 13:32:57 -0400 Subject: [PATCH 64/81] bump spack sha to grab logger changes Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index 32c96622d74..317dc8b9264 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=1d2b11a52d581a540a47e2efbc0d71838e6e553f +SPACK_CHECKOUT_VERSION=9b21172b552ab01c4c2844f6c066de904f807ae0 SPACK_CHECKOUT_REPO=spack/spack From bc4eda752aa81973bef7a9e9b52ca25bf7737ddd Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 27 May 2026 17:29:53 -0400 Subject: [PATCH 65/81] no depends on c for win gpg Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/win_gpg/package.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/repos/spack_repo/builtin/packages/win_gpg/package.py b/repos/spack_repo/builtin/packages/win_gpg/package.py index e0aa633ade2..168432988e4 100644 --- a/repos/spack_repo/builtin/packages/win_gpg/package.py +++ b/repos/spack_repo/builtin/packages/win_gpg/package.py @@ -25,8 +25,6 @@ class WinGpg(Package): version("2.4.5", sha256="249ab87bd06abea3140054089bad44d9a5d1531413590576da609142db2673ec") - depends_on("c", type="build") - @classmethod def determine_version(cls, exe): output = Executable(exe)("--version", output=str, error=str) From 85962e7d329575321fc38ef4ad0b699dc10e6f69 Mon Sep 17 00:00:00 2001 From: John Parent Date: Thu, 28 May 2026 18:09:25 -0400 Subject: [PATCH 66/81] bump wrapper and sha Signed-off-by: John Parent --- .ci/env | 2 +- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/env b/.ci/env index 317dc8b9264..ca4be9530e7 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=9b21172b552ab01c4c2844f6c066de904f807ae0 +SPACK_CHECKOUT_VERSION=f114d13f2426dedaea6f2a75583151075a61851d SPACK_CHECKOUT_REPO=spack/spack diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index e97ba111f63..f98a3947a50 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="1220713c8ca38d1ef745fac5c2b77ff678f71c53") + version("1.0", commit="b9dcab5ac68d6dc9d8ce09d0a440f15b9d6114f3") depends_on("msvc", when="platform=windows", type="build") From 50a5fbd2025f4acd48cbe71f4699a194e3ce667d Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 29 May 2026 11:42:02 -0400 Subject: [PATCH 67/81] another wrapper bump Fixes python Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index f98a3947a50..8a58320397b 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="b9dcab5ac68d6dc9d8ce09d0a440f15b9d6114f3") + version("1.0", commit="c37bfbd80273ebd99a48b81efa4d9eb02b85af57") depends_on("msvc", when="platform=windows", type="build") From 5bf1c8e6a169b7c7803b077c0366d00390414e74 Mon Sep 17 00:00:00 2001 From: John Parent Date: Sun, 31 May 2026 18:30:18 -0400 Subject: [PATCH 68/81] New core change requirement Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index ca4be9530e7..b191cd53f4c 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=f114d13f2426dedaea6f2a75583151075a61851d +SPACK_CHECKOUT_VERSION=dada07e71ad3e3ff3a8c462d9e29daa85a27d522 SPACK_CHECKOUT_REPO=spack/spack From 6093eee4a1e63651c5d7fcead1f08e21936d9e2b Mon Sep 17 00:00:00 2001 From: John Parent Date: Sun, 31 May 2026 18:30:27 -0400 Subject: [PATCH 69/81] Make msvc a run dep Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 8a58320397b..a5ec21f8b90 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -59,7 +59,7 @@ class CompilerWrapper(Package, NMakePackage): # version("develop", branch="main") version("1.0", commit="c37bfbd80273ebd99a48b81efa4d9eb02b85af57") - depends_on("msvc", when="platform=windows", type="build") + depends_on("msvc", when="platform=windows", type=("build", "run")) def bin_dir(self) -> pathlib.Path: # This adds an extra "spack" subdir, so that the script and symlinks don't get From 6347fbb2c3a7f416326ae7147c2011601e63a33e Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 10:30:59 -0400 Subject: [PATCH 70/81] bump spack sha Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index b191cd53f4c..d4c58863cb5 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=dada07e71ad3e3ff3a8c462d9e29daa85a27d522 +SPACK_CHECKOUT_VERSION=23650f07a110437b112c4125511a0668bc2642f1 SPACK_CHECKOUT_REPO=spack/spack From 2ce0753cc07c9c0c6a35234e4839224259319047 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 11:25:22 -0400 Subject: [PATCH 71/81] bump compiler wrapper Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index a5ec21f8b90..7a47e5679b3 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="c37bfbd80273ebd99a48b81efa4d9eb02b85af57") + version("1.0", commit="8a84f27a6c90452816fff7fa78e96eeb0a923768") depends_on("msvc", when="platform=windows", type=("build", "run")) From 3758588f698dd45212f4c14a3f55126bd5ae1a72 Mon Sep 17 00:00:00 2001 From: John Parent Date: Mon, 1 Jun 2026 21:44:54 -0400 Subject: [PATCH 72/81] bump wrapper Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 7a47e5679b3..9c82b4111b5 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="8a84f27a6c90452816fff7fa78e96eeb0a923768") + version("1.0", commit="1fa40545bf9d012948ad36124da73325ff906afa") depends_on("msvc", when="platform=windows", type=("build", "run")) From 2258e3c8384f1160a8d0b57954bcce8bb4be884d Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 15:02:40 -0400 Subject: [PATCH 73/81] Add run context for msvc and use properly in spack core Signed-off-by: John Parent --- .ci/env | 2 +- repos/spack_repo/builtin/packages/msvc/package.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index d4c58863cb5..b676ee4ca52 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=23650f07a110437b112c4125511a0668bc2642f1 +SPACK_CHECKOUT_VERSION=4672cd3c53973159d74f5519a2760dd9da777dc9 SPACK_CHECKOUT_REPO=spack/spack diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index e2fcf5ad083..2cd13ee52a6 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -140,6 +140,9 @@ def setup_dependent_build_environment( else: env.set_path(env_var, int_env[env_var].split(os.pathsep)) + def setup_dependent_run_environment(self, env: EnvironmentModifications, dependent_spec: Spec) -> None: + self.setup_dependent_build_environment(env=env, dependent_spec=dependent_spec) + def init_msvc(self): # To use the MSVC compilers, VCVARS must be invoked # VCVARS is located at a fixed location, referencable From a1dc8f5ea5f3582abe7f5e997c499371d08210f8 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 15:03:55 -0400 Subject: [PATCH 74/81] bump wrapper to restore dumpbin usage Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 9c82b4111b5..af1b270d49d 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="1fa40545bf9d012948ad36124da73325ff906afa") + version("1.0", commit="7b41382bcb5f8782344a319d95eaf29d2758a904") depends_on("msvc", when="platform=windows", type=("build", "run")) From 0fe3db9576f2abe7f1238bf6ce4bd3e05fa61d61 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 15:33:59 -0400 Subject: [PATCH 75/81] style Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msvc/package.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/msvc/package.py b/repos/spack_repo/builtin/packages/msvc/package.py index 2cd13ee52a6..c0e7c211f70 100644 --- a/repos/spack_repo/builtin/packages/msvc/package.py +++ b/repos/spack_repo/builtin/packages/msvc/package.py @@ -140,7 +140,9 @@ def setup_dependent_build_environment( else: env.set_path(env_var, int_env[env_var].split(os.pathsep)) - def setup_dependent_run_environment(self, env: EnvironmentModifications, dependent_spec: Spec) -> None: + def setup_dependent_run_environment( + self, env: EnvironmentModifications, dependent_spec: Spec + ) -> None: self.setup_dependent_build_environment(env=env, dependent_spec=dependent_spec) def init_msvc(self): From 7645f94c1bc03ac167ed6b6f743bf43a4fd52f8a Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 19:59:37 -0400 Subject: [PATCH 76/81] quote ifort invocation Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch b/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch index a75c0841b4b..5a10367664e 100644 --- a/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch +++ b/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch @@ -37,7 +37,7 @@ index 24bd29d..319153d 100644 + /D _WIN64=1 /D _AMD64_=1 /D AMD64=1 - -+ ++ From c1b2b405b9e2f7000674d74c50dc86d900c09288 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 20:59:34 -0400 Subject: [PATCH 77/81] proper quotes Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch b/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch index 5a10367664e..3ef6f842ef9 100644 --- a/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch +++ b/repos/spack_repo/builtin/packages/msmpi/ifort_nd_compat.patch @@ -37,7 +37,7 @@ index 24bd29d..319153d 100644 + /D _WIN64=1 /D _AMD64_=1 /D AMD64=1 - -+ ++ From 40a314dbf955ddbb4da098a56fad199ae2602835 Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 21:18:58 -0400 Subject: [PATCH 78/81] debug the wrapper Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index af1b270d49d..6dab072f4ab 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -191,6 +191,8 @@ def setup_dependent_build_environment( env.prepend_path("SPACK_COMPILER_WRAPPER_PATH", item) env.set("SPACK_CONTEXT_ROOT", dependent_spec.package.stage.source_path) + if IS_WINDOWS: + env.set("SPACK_DEBUG_WRAPPER", "ON") class GenericBuilder(GenericBuilder, EnvironmentSetup): From 4b3c5c3335ff8ab7bde1b79adca28532d6c72ced Mon Sep 17 00:00:00 2001 From: John Parent Date: Tue, 2 Jun 2026 21:30:27 -0400 Subject: [PATCH 79/81] Support lctg binaries Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index 6dab072f4ab..a979251f31d 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="7b41382bcb5f8782344a319d95eaf29d2758a904") + version("1.0", commit="9d3b269b9822fbf184e7ba0193a14d9b1d6e9fcc") depends_on("msvc", when="platform=windows", type=("build", "run")) From a34824b9ebe84b977030e7a1073c8dd495100f33 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 3 Jun 2026 10:58:57 -0400 Subject: [PATCH 80/81] wrapper bump - coff verify write access permissions Signed-off-by: John Parent --- repos/spack_repo/builtin/packages/compiler_wrapper/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py index a979251f31d..151673ba724 100644 --- a/repos/spack_repo/builtin/packages/compiler_wrapper/package.py +++ b/repos/spack_repo/builtin/packages/compiler_wrapper/package.py @@ -57,7 +57,7 @@ class CompilerWrapper(Package, NMakePackage): version("1.0", sha256="ac876f7600fa6cb0c74ae172ef1c61661aacff03a6befbc7d87e092e2f2233f9") else: # version("develop", branch="main") - version("1.0", commit="9d3b269b9822fbf184e7ba0193a14d9b1d6e9fcc") + version("1.0", commit="51358dd5c37a77b9a5816b6f9c8e3e4f6e07fb78") depends_on("msvc", when="platform=windows", type=("build", "run")) From 072a3f412587d7d0ec78eed01b1a97fe0807a2d5 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 3 Jun 2026 11:21:52 -0400 Subject: [PATCH 81/81] bump core for more wrapper debugging Signed-off-by: John Parent --- .ci/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/env b/.ci/env index b676ee4ca52..5c0b654b90a 100644 --- a/.ci/env +++ b/.ci/env @@ -1,2 +1,2 @@ -SPACK_CHECKOUT_VERSION=4672cd3c53973159d74f5519a2760dd9da777dc9 +SPACK_CHECKOUT_VERSION=df98ad4289a7234bd9a443151d70c3f75668fdcf SPACK_CHECKOUT_REPO=spack/spack