From 0ea7f903b5f56fbf563d70fea2da40798b681c34 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 24 Nov 2016 14:25:58 +0300 Subject: Finalize structure reorganization --- .gitignore | 5 - buildfile | 37 ++-- msvc-common/.gitignore | 5 + msvc-common/msvc-common | 2 +- msvc-common/msvc-filter.cxx | 423 ++++++++++++++++++++++++++++++++++++++++++++ msvc-filter.cxx | 423 -------------------------------------------- 6 files changed, 453 insertions(+), 442 deletions(-) delete mode 100644 .gitignore create mode 100644 msvc-common/.gitignore create mode 100644 msvc-common/msvc-filter.cxx delete mode 100644 msvc-filter.cxx diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b2ffb54..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Compiler/linker output. -# -msvc-filter -*.d -*.o diff --git a/buildfile b/buildfile index 6d1467c..db3f8ea 100644 --- a/buildfile +++ b/buildfile @@ -6,23 +6,34 @@ define sh: file sh{*}: extension = sh{*}: install = bin/ -s = cl-11 cl-12 cl-14 cl-14u2 \ - lib-11 lib-12 lib-14 lib-14u2 \ - link-11 link-12 link-14 link-14u2 \ - msvc-11 msvc-12 msvc-14 msvc-14u2 \ - mt-11 mt-12 mt-14 mt-14u2 \ - rc-11 rc-12 rc-14 rc-14u2 \ - \ - msvc-cl-common msvc-common msvc-lib-common \ - msvc-link-common msvc-mt-common msvc-rc-common +s = cl-11-32 cl-12-32 cl-14u0-32 cl-14u2-32 cl-14u2-64 \ + lib-11-32 lib-12-32 lib-14u0-32 lib-14u2-32 lib-14u2-64 \ + link-11-32 link-12-32 link-14u0-32 link-14u2-32 link-14u2-64 \ + mt-11-32 mt-12-32 mt-14u0-32 mt-14u2-32 mt-14u2-64 \ + rc-11-32 rc-12-32 rc-14u0-32 rc-14u2-32 rc-14u2-64 \ + \ + msvc-dispatch \ + \ + msvc-common/{msvc-cl-common msvc-common msvc-lib-common \ + msvc-link-common msvc-mt-common msvc-rc-common} \ + \ + msvc-11/{msvc-11-32} \ + msvc-12/{msvc-12-32} \ + msvc-14/{msvc-14u0-32 msvc-14u2-32 msvc-14u2-64} -./: exe{msvc-filter} sh{$s} doc{INSTALL LICENSE NEWS README version} \ - file{manifest} -import libs = libbutl%lib{butl} +./: msvc-common/exe{msvc-filter} sh{$s} \ + doc{INSTALL LICENSE NEWS README version} file{manifest} -exe{msvc-filter}: cxx{msvc-filter} $libs +msvc-common/: +{ + import libs = libbutl%lib{butl} + + exe{msvc-filter}: cxx{msvc-filter} $libs +} # Don't install INSTALL file. # doc{INSTALL}@./: install = false + +install.bin.subdirs = true # Recreate subdirectories. diff --git a/msvc-common/.gitignore b/msvc-common/.gitignore new file mode 100644 index 0000000..b2ffb54 --- /dev/null +++ b/msvc-common/.gitignore @@ -0,0 +1,5 @@ +# Compiler/linker output. +# +msvc-filter +*.d +*.o diff --git a/msvc-common/msvc-common b/msvc-common/msvc-common index d571f87..3f2e508 100755 --- a/msvc-common/msvc-common +++ b/msvc-common/msvc-common @@ -52,5 +52,5 @@ function msvc_exec () # ... # POSIX mapping. If is 1 then both stdout and stderr output are read # and filtered. # - "$src_dir/msvc-filter" "$diag" wine "$@" + "$src_dir/msvc-common/msvc-filter" "$diag" wine "$@" } diff --git a/msvc-common/msvc-filter.cxx b/msvc-common/msvc-filter.cxx new file mode 100644 index 0000000..11f3235 --- /dev/null +++ b/msvc-common/msvc-filter.cxx @@ -0,0 +1,423 @@ +// file : msvc-common/msvc-filter.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // timeval +#include + +#include // ios_base::failure +#include +#include // strchr() +#include +#include // move() +#include +#include // max() +#include +#include + +#include // path::traits::realize() +#include // alpha() +#include +#include + +using namespace std; +using namespace butl; + +// Cached mapping of Windows paths to the corresponding POSIX paths. +// +static unordered_map path_cache; + +inline static bool +path_separator (char c) +{ + return c == '\\' || c == '/'; +} + +// Replace absolute Windows paths encountered in the line with their POSIX +// representation. Write the result followed by a newline to the stream. Throw +// ostream::failure on IO failures, system_error on others. +// +static void +filter (const char* s, size_t n, ostream& os) +{ + // Strip the terminating 0xOD character if present. + // + if (n > 0 && s[n - 1] == '\r') + --n; + + // Translate absolute Windows paths back to POSIX. The hard part here is to + // determing the end of the path. For example, the error location has the + // 'X:\...\foo(10):' form. However, we cannot assume that '(' ends the path; + // remember 'Program Files (x86)'. + // + // To sidestep this whole mess we are going to use this trick: instead of + // translating the whole path we will only translate its directory part, + // that is, the longest part that still ends with the directory + // separator. We will also still recognize ':' and '\'' as path terminators + // as well as space if it is the first character in the component. + // + // We also pass the path through realpath in order to get the actual path + // rather than Wine's dosdevices links. + // + const char* b (s); + const char* e (s + n); + + for (;;) + { + const char* p (b); + + // Line tail should be at least 3 characters long to contain an absolute + // Windows path. + // + bool no_path (e - b < 3); + + if (!no_path) + { + // An absolute path must begin with [A-Za-z]:[\/] (like C:\). + // + const char* pe (e - 3); + + for (; p != pe; ++p) + { + if (alpha (p[0]) && p[1] == ':' && path_separator (p[2])) + break; + } + + no_path = p == pe; + } + + // Bail out if we reached the end of the line with no path found. + // + if (no_path) + { + os.write (b, e - b); + os.put ('\n'); + break; + } + + os.write (b, p - b); // Print characters that preceed the path. + + b = p; // Beginnig of the path. + const char* pe (p + 3); // End of the last path component. + + for (p = pe; p != e; ++p) + { + char c (*p); + if (c == ':' || c == '\'' || (p == pe && c == ' ')) + break; + + if (path_separator (c)) + pe = p + 1; + } + + // Convert the Windows directory path to POSIX. First check if the mapping + // is already cached. + // + string d (b, pe - b); + auto i (path_cache.find (d)); + + b = pe; + + if (i == path_cache.end ()) + { + const char* args[] = {"winepath", "-u", d.c_str (), nullptr}; + + // Redirect stderr to /dev/null not to mess the output (read more in + // main()). + // + process pr (args, 0, -1, -2); + ifdstream is (move (pr.in_ofd)); + + string pd; + getline (is, pd); + is.close (); + + // It's unknown what can cause winepath to fail. At least not a + // non-existent path. Anyway will consider it fatal. + // + if (!pr.wait ()) + throw system_error (ECHILD, system_category ()); + + try + { + path::traits::realize (pd); + + assert (!pd.empty ()); + + // Restore the trailing slash. + // + if (pd.back () != '/') + pd += '/'; + + } + catch (const invalid_path&) + { + // The path doesn't exist. Let's keep it as provided by winepath. + } + + i = path_cache.emplace (move (d), move (pd)).first; + } + + os.write (i->second.c_str (), i->second.size ()); + } +} + +int +main (int argc, char* argv[]) +try +{ + auto print_usage = [argv]() + { + cerr << "usage: " << argv[0] + << " [arguments]" << endl; + }; + + if (argc < 2) + { + cerr << "error: diag stream file descriptor expected" << endl; + print_usage (); + return 1; + } + + string d (argv[1]); + if (d != "1" && d != "2") + { + cerr << "error: invalid diag stream file descriptor" << endl; + print_usage (); + return 1; + } + + size_t diag (stoi (d)); + + if (argc < 3) + { + cerr << "error: wine path expected" << endl; + print_usage (); + return 1; + } + + if (argc < 4) + { + cerr << "error: program path expected" << endl; + print_usage (); + return 1; + } + + // After arguments are validated we will not be printing error messages on + // failures not to mess the filtered output of the child Windows process. + // Note that in the case of a failure the text from STDERR can be treated by + // the calling process as a build tool diagnostics so our message most + // likelly would be misinterpreted. + // + // The reason we still print error message on the arguments parsing failure + // is that it is likely to be a program misuse rather than runtime error. + // + + // If diag is 1 then both stdout and stderr of the child process are read and + // filtered (achieved by redirecting stdout to stderr). Otherwise the data + // read from child stdout is proxied to own stdout (sounds redundant but + // prevents Windows process from writing to /dev/null directly which is known + // to be super slow). The filtered data is written to the diag file + // descriptor. + // + process pr (const_cast (&argv[2]), 0, diag == 1 ? 2 : -1, -1); + + // Stream to filter from. + // + ifdstream isf (move (pr.in_efd), fdstream_mode::non_blocking); + + // Stream to proxy from. + // + ifdstream isp ( + diag == 1 ? nullfd : move (pr.in_ofd), fdstream_mode::non_blocking); + + ostream& osf (diag == 1 ? cout : cerr); // Stream to filter to. + ostream* osp (diag == 1 ? nullptr : &cout); // Stream to proxy to. + + // The presense of proxy input and output streams must be correlated. + // + assert (isp.is_open () == (osp != nullptr)); + + // Will be using ostream::write() solely, so badbit is the only one which + // needs to be set. + // + osf.exceptions (ostream::badbit); + + if (osp != nullptr) + osp->exceptions (ostream::badbit); + + const size_t nbuf (8192); + char buf[nbuf + 1]; // Reserve one extra for terminating '\0'. + + bool terminated (false); // Required for Wine bug workaround (see below). + string line; // Incomplete line. + + while (isf.is_open () || isp.is_open ()) + { + // Use timeout to workaround the wineserver bug: sometimes the file + // descriptor that corresponds to the redirected STDERR of a Windows + // process is not closed when that process is terminated. So if STDERR is + // redirected to a pipe (as in our case) the reading peer does not receive + // EOF and hangs forever. We will consider the no-data 100 ms period for an + // exited process to represent such a situation. + // + // Note that it is wineserver who owns the corresponding file descriptor + // not a wine process which runs the Windows program. + // + // Note also that some implementations of select() can modify the timeout + // value so it is essential to reinitialize it prior to every select() + // call. + // + timeval timeout {0, 100000}; + + fd_set rd; + FD_ZERO (&rd); + + if (isf.is_open ()) + FD_SET (isf.fd (), &rd); + + if (isp.is_open ()) + FD_SET (isp.fd (), &rd); + + int r (select (max (isf.fd (), isp.fd ()) + 1, + &rd, + nullptr, + nullptr, + &timeout)); + + if (r == -1) + { + if (errno == EINTR) + continue; + + throw system_error (errno, system_category ()); + } + + // Timeout occured. Apply wineserver bug workaround if required. + // + bool status; + if (r == 0 && pr.try_wait (status)) + { + if (!status) + // Handle the child failure outside the loop. + // + break; + + // Presumably end of the data reached. + // + if (!terminated) + { + // We don't know when the process exited. It possibly wasn't writing + // to the output for quite a long time before terminating a nanosecond + // ago. But let's wait for another timeout to be sure that the process + // has terminated a long (enough) time ago. + // + terminated = true; + continue; + } + + break; + } + + // Proxy the data if requested. + // + if (FD_ISSET (isp.fd (), &rd)) + { + for (;;) + { + // The only leagal way to read from non-blocking ifdstream. + // + streamsize n (isp.readsome (buf, nbuf)); + + if (isp.eof ()) + { + // End of the data to be proxied reached. + // + isp.close (); + break; + } + + if (n == 0) + break; // No data available, try later. + + assert (osp != nullptr); + osp->write (buf, n); + } + } + + // Read & filter. + // + if (FD_ISSET (isf.fd (), &rd)) + { + for (;;) + { + // The only leagal way to read from non-blocking ifdstream. + // + streamsize n (isf.readsome (buf, nbuf)); + + if (isf.eof ()) + { + // End of the data to be filtered reached. + // + isf.close (); + break; + } + + if (n == 0) + break; // No data available, try later. + + // Filter buffer content line by line. Concatenate the line with an + // incomplete one if produced on the previous iteration. Save the last + // line if incomplete (not terminated with '\n'). + // + buf[n] = '\0'; + const char* b (buf); + + for (;;) + { + const char* le (strchr (b, '\n')); + + if (le == nullptr) + { + line += b; + break; + } + + if (!line.empty ()) + { + line.append (b, le - b); + filter (line.c_str (), line.size (), osf); + line.clear (); + } + else + filter (b, le - b, osf); + + b = le + 1; // Skip the newline character. + } + } + } + } + + if (!line.empty ()) + filter (line.c_str (), line.size (), osf); + + isf.close (); + isp.close (); + + // Passing through the exact child process exit status on failure tends to be + // a bit hairy as involves handling the situation when the process is + // terminated with a signal and so exit code is unavailable. Lets implement + // when really required. + // + return pr.wait () ? 0 : 1; +} +catch (const ios_base::failure&) +{ + return 1; +} +// Also handles process_error exception (derived from system_error). +// +catch (const system_error&) +{ + return 1; +} diff --git a/msvc-filter.cxx b/msvc-filter.cxx deleted file mode 100644 index a0555cf..0000000 --- a/msvc-filter.cxx +++ /dev/null @@ -1,423 +0,0 @@ -// file : msvc-filter/msvc-filter.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include // timeval -#include - -#include // ios_base::failure -#include -#include // strchr() -#include -#include // move() -#include -#include // max() -#include -#include - -#include // path::traits::realize() -#include // alpha() -#include -#include - -using namespace std; -using namespace butl; - -// Cached mapping of Windows paths to the corresponding POSIX paths. -// -static unordered_map path_cache; - -inline static bool -path_separator (char c) -{ - return c == '\\' || c == '/'; -} - -// Replace absolute Windows paths encountered in the line with their POSIX -// representation. Write the result followed by a newline to the stream. Throw -// ostream::failure on IO failures, system_error on others. -// -static void -filter (const char* s, size_t n, ostream& os) -{ - // Strip the terminating 0xOD character if present. - // - if (n > 0 && s[n - 1] == '\r') - --n; - - // Translate absolute Windows paths back to POSIX. The hard part here is to - // determing the end of the path. For example, the error location has the - // 'X:\...\foo(10):' form. However, we cannot assume that '(' ends the path; - // remember 'Program Files (x86)'. - // - // To sidestep this whole mess we are going to use this trick: instead of - // translating the whole path we will only translate its directory part, - // that is, the longest part that still ends with the directory - // separator. We will also still recognize ':' and '\'' as path terminators - // as well as space if it is the first character in the component. - // - // We also pass the path through realpath in order to get the actual path - // rather than Wine's dosdevices links. - // - const char* b (s); - const char* e (s + n); - - for (;;) - { - const char* p (b); - - // Line tail should be at least 3 characters long to contain an absolute - // Windows path. - // - bool no_path (e - b < 3); - - if (!no_path) - { - // An absolute path must begin with [A-Za-z]:[\/] (like C:\). - // - const char* pe (e - 3); - - for (; p != pe; ++p) - { - if (alpha (p[0]) && p[1] == ':' && path_separator (p[2])) - break; - } - - no_path = p == pe; - } - - // Bail out if we reached the end of the line with no path found. - // - if (no_path) - { - os.write (b, e - b); - os.put ('\n'); - break; - } - - os.write (b, p - b); // Print characters that preceed the path. - - b = p; // Beginnig of the path. - const char* pe (p + 3); // End of the last path component. - - for (p = pe; p != e; ++p) - { - char c (*p); - if (c == ':' || c == '\'' || (p == pe && c == ' ')) - break; - - if (path_separator (c)) - pe = p + 1; - } - - // Convert the Windows directory path to POSIX. First check if the mapping - // is already cached. - // - string d (b, pe - b); - auto i (path_cache.find (d)); - - b = pe; - - if (i == path_cache.end ()) - { - const char* args[] = {"winepath", "-u", d.c_str (), nullptr}; - - // Redirect stderr to /dev/null not to mess the output (read more in - // main()). - // - process pr (args, 0, -1, -2); - ifdstream is (move (pr.in_ofd)); - - string pd; - getline (is, pd); - is.close (); - - // It's unknown what can cause winepath to fail. At least not a - // non-existent path. Anyway will consider it fatal. - // - if (!pr.wait ()) - throw system_error (ECHILD, system_category ()); - - try - { - path::traits::realize (pd); - - assert (!pd.empty ()); - - // Restore the trailing slash. - // - if (pd.back () != '/') - pd += '/'; - - } - catch (const invalid_path&) - { - // The path doesn't exist. Let's keep it as provided by winepath. - } - - i = path_cache.emplace (move (d), move (pd)).first; - } - - os.write (i->second.c_str (), i->second.size ()); - } -} - -int -main (int argc, char* argv[]) -try -{ - auto print_usage = [argv]() - { - cerr << "usage: " << argv[0] - << " [arguments]" << endl; - }; - - if (argc < 2) - { - cerr << "error: diag stream file descriptor expected" << endl; - print_usage (); - return 1; - } - - string d (argv[1]); - if (d != "1" && d != "2") - { - cerr << "error: invalid diag stream file descriptor" << endl; - print_usage (); - return 1; - } - - size_t diag (stoi (d)); - - if (argc < 3) - { - cerr << "error: wine path expected" << endl; - print_usage (); - return 1; - } - - if (argc < 4) - { - cerr << "error: program path expected" << endl; - print_usage (); - return 1; - } - - // After arguments are validated we will not be printing error messages on - // failures not to mess the filtered output of the child Windows process. - // Note that in the case of a failure the text from STDERR can be treated by - // the calling process as a build tool diagnostics so our message most - // likelly would be misinterpreted. - // - // The reason we still print error message on the arguments parsing failure - // is that it is likely to be a program misuse rather than runtime error. - // - - // If diag is 1 then both stdout and stderr of the child process are read and - // filtered (achieved by redirecting stdout to stderr). Otherwise the data - // read from child stdout is proxied to own stdout (sounds redundant but - // prevents Windows process from writing to /dev/null directly which is known - // to be super slow). The filtered data is written to the diag file - // descriptor. - // - process pr (const_cast (&argv[2]), 0, diag == 1 ? 2 : -1, -1); - - // Stream to filter from. - // - ifdstream isf (move (pr.in_efd), fdstream_mode::non_blocking); - - // Stream to proxy from. - // - ifdstream isp ( - diag == 1 ? nullfd : move (pr.in_ofd), fdstream_mode::non_blocking); - - ostream& osf (diag == 1 ? cout : cerr); // Stream to filter to. - ostream* osp (diag == 1 ? nullptr : &cout); // Stream to proxy to. - - // The presense of proxy input and output streams must be correlated. - // - assert (isp.is_open () == (osp != nullptr)); - - // Will be using ostream::write() solely, so badbit is the only one which - // needs to be set. - // - osf.exceptions (ostream::badbit); - - if (osp != nullptr) - osp->exceptions (ostream::badbit); - - const size_t nbuf (8192); - char buf[nbuf + 1]; // Reserve one extra for terminating '\0'. - - bool terminated (false); // Required for Wine bug workaround (see below). - string line; // Incomplete line. - - while (isf.is_open () || isp.is_open ()) - { - // Use timeout to workaround the wineserver bug: sometimes the file - // descriptor that corresponds to the redirected STDERR of a Windows - // process is not closed when that process is terminated. So if STDERR is - // redirected to a pipe (as in our case) the reading peer does not receive - // EOF and hangs forever. We will consider the no-data 100 ms period for an - // exited process to represent such a situation. - // - // Note that it is wineserver who owns the corresponding file descriptor - // not a wine process which runs the Windows program. - // - // Note also that some implementations of select() can modify the timeout - // value so it is essential to reinitialize it prior to every select() - // call. - // - timeval timeout {0, 100000}; - - fd_set rd; - FD_ZERO (&rd); - - if (isf.is_open ()) - FD_SET (isf.fd (), &rd); - - if (isp.is_open ()) - FD_SET (isp.fd (), &rd); - - int r (select (max (isf.fd (), isp.fd ()) + 1, - &rd, - nullptr, - nullptr, - &timeout)); - - if (r == -1) - { - if (errno == EINTR) - continue; - - throw system_error (errno, system_category ()); - } - - // Timeout occured. Apply wineserver bug workaround if required. - // - bool status; - if (r == 0 && pr.try_wait (status)) - { - if (!status) - // Handle the child failure outside the loop. - // - break; - - // Presumably end of the data reached. - // - if (!terminated) - { - // We don't know when the process exited. It possibly wasn't writing - // to the output for quite a long time before terminating a nanosecond - // ago. But let's wait for another timeout to be sure that the process - // has terminated a long (enough) time ago. - // - terminated = true; - continue; - } - - break; - } - - // Proxy the data if requested. - // - if (FD_ISSET (isp.fd (), &rd)) - { - for (;;) - { - // The only leagal way to read from non-blocking ifdstream. - // - streamsize n (isp.readsome (buf, nbuf)); - - if (isp.eof ()) - { - // End of the data to be proxied reached. - // - isp.close (); - break; - } - - if (n == 0) - break; // No data available, try later. - - assert (osp != nullptr); - osp->write (buf, n); - } - } - - // Read & filter. - // - if (FD_ISSET (isf.fd (), &rd)) - { - for (;;) - { - // The only leagal way to read from non-blocking ifdstream. - // - streamsize n (isf.readsome (buf, nbuf)); - - if (isf.eof ()) - { - // End of the data to be filtered reached. - // - isf.close (); - break; - } - - if (n == 0) - break; // No data available, try later. - - // Filter buffer content line by line. Concatenate the line with an - // incomplete one if produced on the previous iteration. Save the last - // line if incomplete (not terminated with '\n'). - // - buf[n] = '\0'; - const char* b (buf); - - for (;;) - { - const char* le (strchr (b, '\n')); - - if (le == nullptr) - { - line += b; - break; - } - - if (!line.empty ()) - { - line.append (b, le - b); - filter (line.c_str (), line.size (), osf); - line.clear (); - } - else - filter (b, le - b, osf); - - b = le + 1; // Skip the newline character. - } - } - } - } - - if (!line.empty ()) - filter (line.c_str (), line.size (), osf); - - isf.close (); - isp.close (); - - // Passing through the exact child process exit status on failure tends to be - // a bit hairy as involves handling the situation when the process is - // terminated with a signal and so exit code is unavailable. Lets implement - // when really required. - // - return pr.wait () ? 0 : 1; -} -catch (const ios_base::failure&) -{ - return 1; -} -// Also handles process_error exception (derived from system_error). -// -catch (const system_error&) -{ - return 1; -} -- cgit v1.1