From f4ed3e569cb5ebae855ea5309bfc17aa6b35874a Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Dec 2014 07:35:54 +0200 Subject: Initial implementation of dependency injection (g++ -M output) --- build/cxx/rule.cxx | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 3 deletions(-) (limited to 'build/cxx/rule.cxx') diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index 9f8f1ae..31c27dc 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -4,15 +4,17 @@ #include +#include // size_t +#include // exit +#include #include #include -#include +#include + #include #include -#include - using namespace std; namespace build @@ -59,9 +61,147 @@ namespace build if (o.path ().empty ()) o.path (path (o.name () + ".o")); + // Inject additional prerequisites. + // + // @@ If this failed, saying that the rule did not match is + // not quite correct. + // + if (!inject_prerequisites (o, *s)) + return recipe (); + return recipe (&update); } + // Return the next make prerequisite starting from the specified + // position and update position to point to the start of the + // following prerequisite or l.size() if there are none left. + // + static string + next (const string& l, size_t& p) + { + size_t n (l.size ()); + + // Skip leading spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Lines containing multiple prerequisites are 80 characters max. + // + string r; + r.reserve (n); + + // Scan the next prerequisite while watching out for escape sequences. + // + for (; p != n && l[p] != ' '; p++) + { + char c (l[p]); + + if (c == '\\') + c = l[++p]; + + r += c; + } + + // Skip trailing spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Skip final '\'. + // + if (p == n - 1 && l[p] == '\\') + p++; + + return r; + } + + bool compile:: + inject_prerequisites (obj& o, const cxx& s) const + { + const char* args[] = { + "g++-4.9", + "-std=c++11", + "-I..", + "-M", + "-MG", // Treat missing headers as generated. + "-MQ", "*", // Quoted target (older version can't handle empty name). + s.path ().string ().c_str (), + nullptr}; + + try + { + process pr (args, false, false, true); + bool r (true); + + __gnu_cxx::stdio_filebuf fb (pr.in_ofd, ios_base::in); + istream is (&fb); + + for (bool first (true); !is.eof (); ) + { + string l; + getline (is, l); + + if (is.fail () && !is.eof ()) + { + cerr << "warning: io error while parsing output" << endl; + r = false; + break; + } + + size_t p (0); + + if (first) + { + // Empty output usually means 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. + } + + while (p != l.size ()) + { + path d (next (l, p)); + + // If there is no extension (e.g., std C++ headers), then + // assume it is a header. Otherwise, let the normall + // mechanism to figure the type from the extension. + // + + // @@ TODO: + // + // - memory leak + + hxx& h (*new hxx (d.leaf ().base ().string ())); + h.path (d); + + o.prerequisite (h); + } + } + + //@@ Any diagnostics if wait() returns false. Or do we assume + // the child process issued something? + // + return pr.wait () && r; + } + catch (const process_error& e) + { + cerr << "warning: unable to execute '" << args[0] << "': " << + e.what () << endl; + + if (e.child ()) + exit (1); + + return false; + } + } + target_state compile:: update (target& t) { -- cgit v1.1