aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2014-12-11 07:35:54 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2014-12-11 07:35:54 +0200
commitf4ed3e569cb5ebae855ea5309bfc17aa6b35874a (patch)
tree58481ae13a9c5d1c4f6853b59bd66a059592074c
parent5e9eb843f6ccadfb47fa603260783425da9e7805 (diff)
Initial implementation of dependency injection (g++ -M output)
-rw-r--r--build/bd.cxx10
-rw-r--r--build/cxx/rule7
-rw-r--r--build/cxx/rule.cxx146
3 files changed, 155 insertions, 8 deletions
diff --git a/build/bd.cxx b/build/bd.cxx
index e48de21..faae4c4 100644
--- a/build/bd.cxx
+++ b/build/bd.cxx
@@ -22,6 +22,10 @@ namespace build
bool
match (target& t)
{
+ // Because we match the target first and then prerequisites,
+ // any additional dependency information injected by the rule
+ // will be covered as well.
+ //
if (!t.recipe ())
{
for (auto ti (&t.type_id ());
@@ -44,7 +48,7 @@ namespace build
if (!t.recipe ())
{
- cerr << "error: no rule to build target " << t << endl;
+ cerr << "error: no rule to update target " << t << endl;
return false;
}
}
@@ -132,11 +136,7 @@ main (int argc, char* argv[])
cxx::cxx bd_cxx ("bd");
bd_cxx.path (path ("bd.cxx"));
- hxx target ("target");
- target.path (path ("target"));
-
bd_o.prerequisite (bd_cxx);
- bd_o.prerequisite (target);
//
//
diff --git a/build/cxx/rule b/build/cxx/rule
index d4412e4..85352b7 100644
--- a/build/cxx/rule
+++ b/build/cxx/rule
@@ -6,6 +6,9 @@
#define BUILD_CXX_RULE
#include <build/rule>
+#include <build/native>
+
+#include <build/cxx/target>
namespace build
{
@@ -23,6 +26,10 @@ namespace build
static target_state
update (target&);
+
+ private:
+ bool
+ inject_prerequisites (obj&, const cxx&) const;
};
class link: public rule
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 <build/cxx/rule>
+#include <cstddef> // size_t
+#include <cstdlib> // exit
+#include <string>
#include <vector>
#include <iostream>
-#include <build/native>
+#include <ext/stdio_filebuf.h>
+
#include <build/process>
#include <build/timestamp>
-#include <build/cxx/target>
-
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<char> 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)
{