From 939beb11a5ccf58d7fe79a809a1b592c5c9143c0 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 11 Nov 2021 13:20:30 +0200 Subject: Add support for dynamic dependencies in ad hoc Buildscript recipes Specifically, add the new `depdb dyndep` builtin that can be used to extract dynamic dependencies from a program run or a file. For example: obje{hello.o}: cxx{hello} {{ s = $path($<[0]) depdb dyndep $cxx.poptions $cc.poptions --what=header --default-prereq-type=h -- $cxx.path $cxx.poptions $cc.poptions $cxx.mode -M -MG $s diag c++ ($<[0]) o = $path($>) $cxx.path $cxx.poptions $cc.poptions $cc.coptions $cxx.coptions $cxx.mode -o $o -c $s }} Currently only the `make` dependency format is supported. --- libbuild2/make-parser.cxx | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 libbuild2/make-parser.cxx (limited to 'libbuild2/make-parser.cxx') diff --git a/libbuild2/make-parser.cxx b/libbuild2/make-parser.cxx new file mode 100644 index 0000000..d076a0a --- /dev/null +++ b/libbuild2/make-parser.cxx @@ -0,0 +1,137 @@ +// file : libbuild2/make-parser.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include + +namespace build2 +{ + auto make_parser:: + next (const string& l, + size_t& p, + const location& ll, + bool strict) -> pair + { + assert (state != end); + + pair r ( + next (l, p, !strict ? state == prereqs : optional ())); + + type t (state == prereqs ? type::prereq : type::target); + + // Deal with the end. + // + if (r.second) + { + if (state == begin && r.first.empty ()) + ; // Skip leading blank line. + else + { + if (state != prereqs) + fail (ll) << "end of make dependency declaration before ':'"; + + state = end; + } + } + // Deal with the first target. + // + else if (state == begin && !r.first.empty ()) + state = targets; + + // Deal with `:`. + // + if (p != l.size () && l[p] == ':') + { + switch (state) + { + case begin: fail (ll) << "':' before make target"; break; + case targets: state = prereqs; break; + case prereqs: fail (ll) << "':' after make prerequisite"; break; + case end: break; + } + + if (++p == l.size ()) + state = end; // Not a mere optimization: the caller will get next line. + } + + return pair (t, move (r.first)); + } + + pair make_parser:: + next (const string& l, size_t& p, optional prereq) + { + size_t n (l.size ()); + + // Skip leading spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Lines containing multiple targets/prerequisites are customarily 80 + // characters max. + // + string r; + r.reserve (n - p); + + // Scan the next target/prerequisite while watching out for escape + // sequences. + // + // @@ Can't we do better for the (common) case where nothing is escaped? + // + for (char c, q (prereq && *prereq ? '\0' : ':'); + p != n && (c = l[p]) != ' ' && c != q; ) + { + // If we have another character, then handle the escapes. + // + if (++p != n) + { + if (c == '\\') + { + // This may or may not be an escape sequence depending on whether + // what follows is "escapable". + // + switch (c = l[p]) + { + case '\\': + case ' ': + case ':': ++p; break; + default: c = '\\'; // Restore. + } + } + else if (c == '$') + { + // Got to be another (escaped) '$'. + // + if (l[p] == '$') + ++p; + } + } + // Note that the newline escape is not necessarily separated with space. + // + else if (c == '\\') + { + --p; + break; + } + + r += c; + } + + // Skip trailing spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Skip final '\' and determine if this is the end. + // + bool e (false); + if (p == n - 1) + { + if (l[p] == '\\') + p++; + } + else if (p == n) + e = true; + + return pair (move (r), e); + } +} -- cgit v1.1