From 72e9ec3d7028765836851b515d80816f2da74060 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 27 Oct 2022 11:00:47 +0200 Subject: Initial work on child process diagnostics buffering Currently this is implemented for C/C++ compile and link rules. --- libbuild2/cc/compile-rule.cxx | 91 ++++++++++++++++++++++-------------- libbuild2/cc/link-rule.cxx | 106 +++++++++++++++++++++++++++--------------- libbuild2/cc/msvc.cxx | 25 +++++++--- 3 files changed, 141 insertions(+), 81 deletions(-) (limited to 'libbuild2/cc') diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index fda97a0..8d507e7 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -264,6 +264,10 @@ namespace build2 compile_rule:: compile_rule (data&& d, const scope& rs) : common (move (d)), + // + // @@ TODO: split into rule_name (string) and rule_version (integer) + // on next increment. + // rule_id (string (x) += ".compile 6") { // Locate the header cache (see enter_header() for details). @@ -3961,6 +3965,8 @@ namespace build2 // diagnostics (if things go badly we will restart with this // support). // + // @@ TODO: diag_buffer + // if (drmp == nullptr) // Dependency info goes to stdout. { assert (!sense_diag); // Note: could support with fdselect(). @@ -3970,7 +3976,7 @@ namespace build2 // pr = process ( cpath, - args.data (), + args, 0, -1, cclass == compiler_class::msvc ? 1 : gen ? 2 : -2, @@ -3980,7 +3986,7 @@ namespace build2 else // Dependency info goes to a temporary file. { pr = process (cpath, - args.data (), + args, mod_mapper ? -1 : 0, mod_mapper ? -1 : 2, // Send stdout to stderr. gen ? 2 : sense_diag ? -1 : -2, @@ -4890,10 +4896,10 @@ namespace build2 print_process (args); // We don't want to see warnings multiple times so ignore all - // diagnostics. + // diagnostics (thus no need for diag_buffer). // pr = process (cpath, - args.data (), + args, 0, -1, -2, nullptr, // CWD env.empty () ? nullptr : env.data ()); @@ -6327,7 +6333,7 @@ namespace build2 // Filter cl.exe noise (msvc.cxx). // void - msvc_filter_cl (ifdstream&, const path& src); + msvc_filter_cl (diag_buffer&, const path& src); // Append header unit-related options. // @@ -6674,7 +6680,7 @@ namespace build2 small_vector module_args; // Module options storage. size_t out_i (0); // Index of the -o option. - size_t lang_n (0); // Number of lang options. + //size_t lang_n (0); // Number of lang options. @@ TMP switch (cclass) { @@ -7025,7 +7031,7 @@ namespace build2 args.push_back ("-c"); } - lang_n = append_lang_options (args, md); + /*lang_n = */append_lang_options (args, md); // @@ TMP if (md.pp == preprocessed::all) { @@ -7074,6 +7080,8 @@ namespace build2 // the source file, not its preprocessed version (so that it's easy to // copy and re-run, etc). Only at level 3 and above print the real deal. // + // @@ TODO: why don't we print env (here and/or below)? Also link rule. + // if (verb == 1) text << x_name << ' ' << s; else if (verb == 2) @@ -7081,12 +7089,17 @@ namespace build2 // If we have the (partially) preprocessed output, switch to that. // + // But we remember the original source/position to restore later. + // bool psrc (md.psrc); bool ptmp (psrc && md.psrc.temporary); + pair osrc; if (psrc) { args.pop_back (); // nullptr + osrc.second = args.back (); args.pop_back (); // sp + osrc.first = args.size (); sp = &md.psrc.path (); @@ -7096,6 +7109,8 @@ namespace build2 { case compiler_type::gcc: { + // @@ TMP +#if 0 // The -fpreprocessed is implied by .i/.ii. But not when compiling // a header unit (there is no .hi/.hii). // @@ -7109,6 +7124,15 @@ namespace build2 // for (; lang_n != 0; --lang_n) args.pop_back (); +#else + // -fpreprocessed is implied by .i/.ii unless compiling a header + // unit (there is no .hi/.hii). Also, we would need to pop -x + // since it takes precedence over the extension, which would mess + // up our osrc logic. So in the end it feels like always passing + // explicit -fpreprocessed is the way to go. + // + args.push_back ("-fpreprocessed"); +#endif args.push_back ("-fdirectives-only"); break; @@ -7145,6 +7169,8 @@ namespace build2 if (verb >= 3) print_process (args); + diag_buffer dbuf (ctx); + // @@ DRYRUN: Currently we discard the (partially) preprocessed file on // dry-run which is a waste. Even if we keep the file around (like we do // for the error case; see above), we currently have no support for @@ -7163,45 +7189,36 @@ namespace build2 file_cache::read psrcr (psrc ? md.psrc.open () : file_cache::read ()); // VC cl.exe sends diagnostics to stdout. It also prints the file - // name being compiled as the first line. So for cl.exe we redirect - // stdout to a pipe, filter that noise out, and send the rest to - // stderr. + // name being compiled as the first line. So for cl.exe we filter + // that noise out. // - // For other compilers redirect stdout to stderr, in case any of - // them tries to pull off something similar. For sane compilers this - // should be harmless. + // For other compilers also redirect stdout to stderr, in case any + // of them tries to pull off something similar. For sane compilers + // this should be harmless. // bool filter (ctype == compiler_type::msvc); process pr (cpath, - args.data (), - 0, (filter ? -1 : 2), 2, + args, + 0, 2, dbuf.open (args[0], filter), nullptr, // CWD env.empty () ? nullptr : env.data ()); if (filter) - { - try - { - ifdstream is ( - move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit); - - msvc_filter_cl (is, *sp); + msvc_filter_cl (dbuf, *sp); - // If anything remains in the stream, send it all to stderr. - // Note that the eof check is important: if the stream is at - // eof, this and all subsequent writes to the diagnostics stream - // will fail (and you won't see a thing). - // - if (is.peek () != ifdstream::traits_type::eof ()) - diag_stream_lock () << is.rdbuf (); + dbuf.read (); - is.close (); - } - catch (const io_error&) {} // Assume exits with error. + // Restore the original source if we switched to preprocessed. + // + if (psrc) + { + args.resize (osrc.first); + args.push_back (osrc.second); + args.push_back (nullptr); } - run_finish (args, pr); + dbuf.finish (args, pr); } catch (const process_error& e) { @@ -7252,12 +7269,14 @@ namespace build2 try { process pr (cpath, - args.data (), - 0, 2, 2, + args, + 0, 2, dbuf.open (args[0]), nullptr, // CWD env.empty () ? nullptr : env.data ()); - run_finish (args, pr); + dbuf.read (); + + dbuf.finish (args, pr); } catch (const process_error& e) { diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index a5fa7bc..5d212e6 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -254,6 +254,10 @@ namespace build2 link_rule:: link_rule (data&& d) : common (move (d)), + // + // @@ TODO: split into rule_name (string) and rule_version (integer) + // on next increment. + // rule_id (string (x) += ".link 3") { } @@ -2671,7 +2675,7 @@ namespace build2 // Filter link.exe noise (msvc.cxx). // void - msvc_filter_link (ifdstream&, const file&, otype); + msvc_filter_link (diag_buffer&, const file&, otype); // Translate target CPU to the link.exe/lib.exe /MACHINE option. // @@ -2879,6 +2883,8 @@ namespace build2 env_ptrs.push_back (nullptr); } + diag_buffer dbuf (ctx); + // If targeting Windows, take care of the manifest. // path manifest; // Manifest itself (msvc) or compiled object file. @@ -2929,12 +2935,15 @@ namespace build2 try { + // We assume that what we write to stdin is small enough to + // fit into the pipe's buffer without blocking. + // process pr (rc, args, - -1 /* stdin */, - 1 /* stdout */, - 2 /* stderr */, - nullptr /* cwd */, + -1 /* stdin */, + 1 /* stdout */, + dbuf.open (args[0]) /* stderr */, + nullptr /* cwd */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); try @@ -2960,7 +2969,8 @@ namespace build2 // was caused by that and let run_finish() deal with it. } - run_finish (args, pr); + dbuf.read (); + dbuf.finish (args, pr, 2 /* verbosity */); } catch (const process_error& e) { @@ -3823,8 +3833,21 @@ namespace build2 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. + // + // We also need to save the original for the diag_buffer::close() call + // below if at verbosity level 1. + // + cstrings oargs; + // Adjust linker parallelism. // + // Note that we are not going to bother with oargs for this. + // string jobs_arg; scheduler::alloc_guard jobs_extra; @@ -3875,12 +3898,6 @@ namespace build2 } } - // 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 @@ -3966,14 +3983,15 @@ namespace build2 fail << "unable to write to " << f << ": " << e; } + if (verb == 1) + oargs = args; + // 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 @@ -3996,51 +4014,48 @@ namespace build2 { // VC tools (both lib.exe and link.exe) send diagnostics to stdout. // Also, link.exe likes to print various gratuitous messages. So for - // link.exe we redirect stdout to a pipe, filter that noise out, and - // send the rest to stderr. + // link.exe we filter that noise out. // // For lib.exe (and any other insane linker that may try to pull off // something like this) we are going to redirect stdout to stderr. // For sane compilers this should be harmless. // // Note that we don't need this for LLD's link.exe replacement which - // is quiet. + // is thankfully quiet. // bool filter (tsys == "win32-msvc" && !lt.static_library () && cast (rs["bin.ld.id"]) != "msvc-lld"); process pr (*ld, - args.data (), - 0 /* stdin */, - (filter ? -1 : 2) /* stdout */, - 2 /* stderr */, - nullptr /* cwd */, + args, + 0 /* stdin */, + 2 /* stdout */, + dbuf.open (args[0], filter) /* stderr */, + nullptr /* cwd */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); if (filter) + msvc_filter_link (dbuf, t, ot); + + dbuf.read (); + { - try - { - ifdstream is ( - move (pr.in_ofd), fdstream_mode::text, ifdstream::badbit); + bool e (pr.wait ()); - msvc_filter_link (is, t, ot); +#ifdef _WIN32 + // Keep the options file if we have shown it. + // + if (!e && verb > 2) + trm.cancel (); +#endif - // If anything remains in the stream, send it all to stderr. - // Note that the eof check is important: if the stream is at - // eof, this and all subsequent writes to the diagnostics stream - // will fail (and you won't see a thing). - // - if (is.peek () != ifdstream::traits_type::eof ()) - diag_stream_lock () << is.rdbuf (); + dbuf.close (oargs.empty () ? args : oargs, *pr.exit); - is.close (); - } - catch (const io_error&) {} // Assume exits with error. + if (!e) + throw failed (); } - run_finish (args, pr); jobs_extra.deallocate (); } catch (const process_error& e) @@ -4100,10 +4115,25 @@ namespace build2 print_process (args); if (!ctx.dry_run) + { +#if 0 run (rl, args, dir_path () /* cwd */, env_ptrs.empty () ? nullptr : env_ptrs.data ()); +#else + process pr (rl, + args, + 0 /* stdin */, + 1 /* stdout */, + dbuf.open (args[0]) /* stderr */, + nullptr /* cwd */, + env_ptrs.empty () ? nullptr : env_ptrs.data ()); + + dbuf.read (); + dbuf.finish (args, pr); +#endif + } } // For Windows generate (or clean up) rpath-emulating assembly. diff --git a/libbuild2/cc/msvc.cxx b/libbuild2/cc/msvc.cxx index f95cab0..9aff347 100644 --- a/libbuild2/cc/msvc.cxx +++ b/libbuild2/cc/msvc.cxx @@ -164,18 +164,21 @@ namespace build2 // Filter cl.exe and link.exe noise. // + // Note: must be followed with the dbuf.read() call. + // void - msvc_filter_cl (ifdstream& is, const path& src) + msvc_filter_cl (diag_buffer& dbuf, const path& src) + try { // While it appears VC always prints the source name (event if the // file does not exist), let's do a sanity check. Also handle the // command line errors/warnings which come before the file name. // - for (string l; !eof (getline (is, l)); ) + for (string l; !eof (getline (dbuf.is, l)); ) { if (l != src.leaf ().string ()) { - diag_stream_lock () << l << endl; + dbuf.write (l, true /* newline */); if (msvc_sense_diag (l, 'D').first != string::npos) continue; @@ -184,14 +187,19 @@ namespace build2 break; } } + catch (const io_error&) + { + // Let the following diag_buffer::read() call deal with this. + } void - msvc_filter_link (ifdstream& is, const file& t, otype lt) + msvc_filter_link (diag_buffer& dbuf, const file& t, otype lt) + try { // Filter lines until we encounter something we don't recognize. We also // have to assume the messages can be translated. // - for (string l; getline (is, l); ) + for (string l; getline (dbuf.is, l); ) { // " Creating library foo\foo.dll.lib and object foo\foo.dll.exp" // @@ -216,12 +224,15 @@ namespace build2 // /INCREMENTAL causes linker to sometimes issue messages but now I // can't quite reproduce it. - // - diag_stream_lock () << l << endl; + dbuf.write (l, true /* newline */); break; } } + catch (const io_error&) + { + // Let the following diag_buffer::read() call deal with this. + } void msvc_extract_header_search_dirs (const strings& v, dir_paths& r) -- cgit v1.1