From 5b2e0bc7fd53e3329cad75b8d8361daaa8bd2465 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 27 Feb 2019 10:11:59 +0200 Subject: Use options (aka response) file on Windows if link command line is too long --- build2/cc/link-rule.cxx | 121 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 10 deletions(-) (limited to 'build2/cc/link-rule.cxx') diff --git a/build2/cc/link-rule.cxx b/build2/cc/link-rule.cxx index 30f1df8..4a81874 100644 --- a/build2/cc/link-rule.cxx +++ b/build2/cc/link-rule.cxx @@ -6,6 +6,7 @@ #include #include // exit() +#include // strlen() #include #include // file_exists() @@ -2304,9 +2305,14 @@ namespace build2 args[0] = ld->recall_string (); - // Append input files. The same logic as during hashing above. + // Append input files noticing the position of the first. // - // See also a similar loop inside append_libraries(). +#ifdef _WIN32 + size_t args_input (args.size ()); +#endif + + // The same logic as during hashing above. See also a similar loop + // inside append_libraries(). // for (const prerequisite_target& p: t.prerequisite_targets[a]) { @@ -2344,7 +2350,7 @@ namespace build2 sargs.push_back (relative (manifest).string ()); // Shallow-copy sargs to args. Why not do it as we go along pushing into - // sargs? Because of potential reallocations. + // sargs? Because of potential reallocations in sargs. // for (const string& a: sargs) args.push_back (a.c_str ()); @@ -2419,17 +2425,103 @@ namespace build2 try_rmfile (relt, true); } - // Remove the target file if any of the subsequent (after ld) actions - // fail or if ld fails but does not clean up its mess (like link.exe). - // If we don't do that, then we will end up with a broken build that is - // up-to-date. + // Remove the target file if any of the subsequent (after the linker) + // actions fail or if the linker fails but does not clean up its mess + // (like link.exe). If we don't do that, then we will end up with a + // broken build that is up-to-date. // auto_rmfile rm (relt); - if (verb >= 2) - print_process (args); - else if (verb) + if (verb == 1) text << (lt.static_library () ? "ar " : "ld ") << t; + else if (verb == 2) + print_process (args); + + // Do any necessary fixups to the command line to make it runnable. + // + // Notice the split in the diagnostics: at verbosity level 1 we print + // the "logical" command line while at level 2 and above -- what we are + // actually executing. + // + // On Windows we need to deal with the command line length limit. The + // best workaround seems to be passing (part of) the command line in an + // "options file" ("response file" in Microsoft's terminology). Both + // Microsoft's link.exe/lib.exe as well as GNU g??.exe/ar.exe support + // the same @ notation (and with a compatible subset of the + // content format; see below). Note also that GCC is smart enough to use + // an options file to call the underlying linker if we called it with + // @. We will also assume that any other linker that we might be + // using supports this notation. + // + // Note that this is a limitation of the host platform, not the target + // (and Wine, where these lines are a bit blurred, does not have this + // length limitation). + // +#ifdef _WIN32 + auto_rmfile trm; + string targ; + { + // Calculate the would-be command line length similar to how process' + // implementation does it. + // + auto quote = [s = string ()] (const char* a) mutable -> const char* + { + return process::quote_argument (a, s); + }; + + size_t n (0); + for (const char* a: args) + { + if (a != nullptr) + { + if (n != 0) + n++; // For the space separator. + + n += strlen (quote (a)); + } + } + + if (n > 32766) // 32768 - "Unicode terminating null character". + { + // Use the .t extension (for "temporary"). + // + const path& f ((trm = auto_rmfile (relt + ".t")).path); + + try + { + ofdstream ofs (f); + + // Both Microsoft and GNU support a space-separated list of + // potentially-quoted arguments. GNU also supports backslash- + // escaping but whether Microsoft supports it is unclear. + // + for (size_t i (args_input), n (args.size () - 1); i != n; ++i) + { + ofs << (i != args_input ? " " : "") << quote (args[i]); + } + + ofs << '\n'; + ofs.close (); + } + catch (const io_error& e) + { + fail << "unable to write " << f << ": " << e; + } + + // Replace input arguments with @file. + // + targ = '@' + f.string (); + args.resize (args_input); + args.push_back (targ.c_str()); + args.push_back (nullptr); + + //@@ TODO: leave .t file if linker failed and verb > 2? + } + } +#endif + + if (verb > 2) + print_process (args); try { @@ -2479,7 +2571,13 @@ namespace build2 // the stack and running destructors). // if (e.child) + { + rm.cancel (); +#ifdef _WIN32 + trm.cancel (); +#endif exit (1); + } throw failed (); } @@ -2598,6 +2696,9 @@ namespace build2 const file& t (xt.as ()); ltype lt (link_type (t)); + //@@ TODO add .t to clean if _WIN32 (currently that would be just too + // messy). + if (lt.executable ()) { if (tclass == "windows") -- cgit v1.1