From fdc21950905d64b2ca1df5a0b2622022beffe922 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Dec 2014 09:45:26 +0200 Subject: Improve diagnostics and error handling g++-4.9 -std=c++14 -g -I.. -o bd bd.cxx target.cxx native.cxx rule.cxx cxx/rule.cxx cxx/target.cxx process.cxx timestamp.cxx path.cxx --- build/bd.cxx | 88 +++++++++++++++++++++++++++++++++++------------------- build/cxx/rule | 2 +- build/cxx/rule.cxx | 55 ++++++++++++++++++---------------- build/diagnostics | 54 +++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 56 deletions(-) create mode 100644 build/diagnostics diff --git a/build/bd.cxx b/build/bd.cxx index faae4c4..c592d64 100644 --- a/build/bd.cxx +++ b/build/bd.cxx @@ -5,7 +5,6 @@ #include // tzset() #include -#include // exit #include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include using namespace std; @@ -38,7 +38,21 @@ namespace build { const rule& ru (rs.first->second); - if (recipe re = ru.match (t)) + recipe re; + + { + auto g ( + make_exception_guard ( + [] (target& t) + { + cerr << "info: while matching a rule for target " << t << endl; + }, + t)); + + re = ru.match (t); + } + + if (re) { t.recipe (re); break; @@ -83,21 +97,23 @@ namespace build } } - try - { - t.state ((ts = t.recipe () (t))); - assert (ts != target_state::unknown); - return ts; - } - catch (const process_error& e) + const recipe& r (t.recipe ()); + { - // Take care of failed children. In a multi-threaded program that - // fork()'ed but did not exec(), it is unwise to try to do any kind - // of cleanup (like unwinding the stack and running destructors). - // - assert (e.child ()); - exit (1); + auto g ( + make_exception_guard ( + [] (target& t) + { + cerr << "info: while building target " << t << endl; + }, + t)); + + ts = r (t); } + + assert (ts != target_state::unknown); + t.state (ts); + return ts; } } @@ -140,24 +156,36 @@ main (int argc, char* argv[]) // // - if (!match (bd)) - return 1; // Diagnostics has already been issued. - - switch (update (bd)) + try { - case target_state::uptodate: + if (!match (bd)) + return 1; // Diagnostics has already been issued. + + switch (update (bd)) { - cerr << "info: target " << bd << " is up to date" << endl; + case target_state::uptodate: + { + cerr << "info: target " << bd << " is up to date" << endl; + break; + } + case target_state::updated: break; + case target_state::failed: + { + cerr << "error: failed to update target " << bd << endl; + return 1; + } + case target_state::unknown: + assert (false); } - case target_state::updated: - break; - case target_state::failed: - { - cerr << "error: failed to update target " << bd << endl; - return 1; - } - case target_state::unknown: - assert (false); + } + catch (const error&) + { + return 1; // Diagnostics has already been issued. + } + catch (const std::exception& e) + { + cerr << "error: " << e.what () << endl; + return 1; } } diff --git a/build/cxx/rule b/build/cxx/rule index 85352b7..4900180 100644 --- a/build/cxx/rule +++ b/build/cxx/rule @@ -28,7 +28,7 @@ namespace build update (target&); private: - bool + void inject_prerequisites (obj&, const cxx&) const; }; diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index 31c27dc..ccc9bb5 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -14,6 +14,7 @@ #include #include +#include using namespace std; @@ -63,11 +64,7 @@ namespace build // Inject additional prerequisites. // - // @@ If this failed, saying that the rule did not match is - // not quite correct. - // - if (!inject_prerequisites (o, *s)) - return recipe (); + inject_prerequisites (o, *s); return recipe (&update); } @@ -114,12 +111,12 @@ namespace build return r; } - bool compile:: + void compile:: inject_prerequisites (obj& o, const cxx& s) const { const char* args[] = { "g++-4.9", - "-std=c++11", + "-std=c++14", "-I..", "-M", "-MG", // Treat missing headers as generated. @@ -130,7 +127,6 @@ namespace build try { process pr (args, false, false, true); - bool r (true); __gnu_cxx::stdio_filebuf fb (pr.in_ofd, ios_base::in); istream is (&fb); @@ -142,27 +138,24 @@ namespace build if (is.fail () && !is.eof ()) { - cerr << "warning: io error while parsing output" << endl; - r = false; - break; + cerr << "error: io error while parsing g++ -M output" << endl; + throw error (); } size_t p (0); if (first) { - // Empty output usually means the wait() call below will return + // Empty output should mean the wait() call below will return // false. // if (l.empty ()) - { - r = false; break; - } - first = false; assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); next (l, (p = 3)); // Skip the source file. + + first = false; } while (p != l.size ()) @@ -185,20 +178,24 @@ namespace build } } - //@@ Any diagnostics if wait() returns false. Or do we assume - // the child process issued something? + // We assume the child process issued some diagnostics. // - return pr.wait () && r; + if (!pr.wait ()) + throw error (); } catch (const process_error& e) { - cerr << "warning: unable to execute '" << args[0] << "': " << + cerr << "error: unable to execute '" << args[0] << "': " << e.what () << endl; + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // if (e.child ()) exit (1); - return false; + throw error (); } } @@ -243,7 +240,7 @@ namespace build const char* args[] = { "g++-4.9", - "-std=c++11", + "-std=c++14", "-I..", "-c", "-o", o.path ().string ().c_str (), @@ -272,8 +269,12 @@ namespace build cerr << "error: unable to execute '" << args[0] << "': " << e.what () << endl; + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // if (e.child ()) - throw; // Let caller terminate us quickly without causing a scene. + exit (1); return target_state::failed; } @@ -356,7 +357,7 @@ namespace build if (!u) return target_state::uptodate; - vector args {"g++-4.9", "-std=c++11", "-o"}; + vector args {"g++-4.9", "-std=c++14", "-o"}; args.push_back (e.path ().string ().c_str ()); @@ -390,8 +391,12 @@ namespace build cerr << "error: unable to execute '" << args[0] << "': " << e.what () << endl; + // In a multi-threaded program that fork()'ed but did not exec(), + // it is unwise to try to do any kind of cleanup (like unwinding + // the stack and running destructors). + // if (e.child ()) - throw; // Let caller terminate us quickly without causing a scene. + exit (1); return target_state::failed; } diff --git a/build/diagnostics b/build/diagnostics new file mode 100644 index 0000000..c8fb169 --- /dev/null +++ b/build/diagnostics @@ -0,0 +1,54 @@ +// file : build/diagnostics -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_DIAGNOSTICS +#define BUILD_DIAGNOSTICS + +#include +#include +#include + +namespace build +{ + // Throw this exception to terminate the build. The handler should + // assume that the diagnostics has already been issued. + // + class error: public std::exception {}; + + // Call a function if there is an exception. + // + template + struct exception_guard; + + template + inline exception_guard> + make_exception_guard (F f, A&&... a) + { + return exception_guard> ( + std::move (f), std::forward_as_tuple (a...)); + } + + template + struct exception_guard> + { + typedef std::tuple T; + + exception_guard (F f, T a): f_ (std::move (f)), a_ (std::move (a)) {} + ~exception_guard () + { + if (std::uncaught_exception ()) + call (std::index_sequence_for ()); + } + + private: + template + void + call (std::index_sequence) {f_ (std::get (a_)...);} + + F f_; + T a_; + }; +} + +#endif // BUILD_DIAGNOSTICS -- cgit v1.1