aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/in/rule.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/in/rule.cxx')
-rw-r--r--libbuild2/in/rule.cxx449
1 files changed, 304 insertions, 145 deletions
diff --git a/libbuild2/in/rule.cxx b/libbuild2/in/rule.cxx
index 2569948..31a9d94 100644
--- a/libbuild2/in/rule.cxx
+++ b/libbuild2/in/rule.cxx
@@ -23,14 +23,14 @@ namespace build2
namespace in
{
bool rule::
- match (action a, target& xt, const string&) const
+ match (action a, target& xt) const
{
tracer trace ("in::rule::match");
if (!xt.is_a<file> ()) // See module init() for details.
return false;
- file& t (static_cast<file&> (xt));
+ file& t (xt.as<file> ());
bool fi (false); // Found in.
for (prerequisite_member p: group_prerequisite_members (a, t))
@@ -47,17 +47,24 @@ namespace build2
if (!fi)
l5 ([&]{trace << "no in file prerequisite for target " << t;});
+ // If we match, derive the file name here instead of in apply() to make
+ // it available early for the in{} prerequisite search (see
+ // install::file_rule::apply_impl() for background).
+ //
+ if (fi)
+ t.derive_path ();
+
return fi;
}
recipe rule::
apply (action a, target& xt) const
{
- file& t (static_cast<file&> (xt));
+ file& t (xt.as<file> ());
- // Derive the file name.
+ // Make sure derived rules assign the path in match().
//
- t.derive_path ();
+ assert (!t.path ().empty ());
// Inject dependency on the output directory.
//
@@ -108,7 +115,7 @@ namespace build2
// Substitution mode.
//
bool strict (strict_);
- if (const string* s = cast_null<string> (t["in.substitution"]))
+ if (const string* s = cast_null<string> (t["in.mode"]))
{
if (*s == "lax")
strict = false;
@@ -116,6 +123,11 @@ namespace build2
fail << "invalid substitution mode '" << *s << "'";
}
+ // Substitution map.
+ //
+ const substitution_map* smap (
+ cast_null<map<string, optional<string>>> (t["in.substitutions"]));
+
// NULL substitutions.
//
optional<string> null;
@@ -157,6 +169,10 @@ namespace build2
l4 ([&]{trace << "substitution mode mismatch forcing update of"
<< t;});
+ // Then additional depdb entries, if any.
+ //
+ perform_update_depdb (a, t, dd);
+
// Then the .in file.
//
if (dd.expect (i.path ()) != nullptr)
@@ -209,39 +225,61 @@ namespace build2
{
// The line format is:
//
- // <ln> <name> <hash>
+ // <ln> <name> <hash>[/<flags>]
//
// Note that <name> can contain spaces (see the constraint check
- // expressions in the version module).
+ // expressions in the version module). That's the reason why we
+ // use the `/` separator for <flags> instead of the more natural
+ // space.
//
char* e (nullptr);
uint64_t ln (strtoull (s->c_str (), &e, 10));
- size_t p1 (*e == ' ' ? e - s->c_str () : string::npos);
- size_t p2 (s->rfind (' '));
+ size_t p1 (*e == ' ' ? e - s->c_str () : string::npos); // <name>
+ size_t p2 (s->rfind (' ')); // <hash>
if (p1 != string::npos && p2 != string::npos && p2 - p1 > 1)
{
- string n (*s, p1 + 1, p2 - p1 - 1);
+ ++p1;
+ string name (*s, p1, p2 - p1);
- // Note that we have to call substitute(), not lookup() since it
- // can be overriden with custom substitution semantics.
- //
- optional<string> v (
- substitute (location (ip, ln), a, t, n, strict, null));
+ ++p2;
+ size_t p3 (s->find ('/', p2)); // <flags>
- assert (v); // Rule semantics change without version increment?
+ optional<uint64_t> flags;
+ if (p3 != string::npos)
+ {
+ uint64_t v (strtoull (s->c_str () + p3 + 1, &e, 10));
+ if (*e == '\0')
+ flags = v;
+ }
- if (s->compare (p2 + 1,
- string::npos,
- sha256 (*v).string ()) == 0)
+ if (p3 == string::npos || flags)
{
- dd_skip++;
- continue;
+ // Note that we have to call substitute(), not lookup() since
+ // it can be overriden with custom substitution semantics.
+ //
+ optional<string> v (
+ substitute (location (ip, ln),
+ a, t,
+ name, flags,
+ strict, smap, null));
+
+ assert (v); // Rule semantics change without version increment?
+
+ if (p3 != string::npos)
+ p3 -= p2; // Hash length.
+
+ if (s->compare (p2, p3, sha256 (*v).string ()) == 0)
+ {
+ dd_skip++;
+ continue;
+ }
+ else
+ l4 ([&]{trace << name << " variable value mismatch forcing "
+ << "update of " << t;});
}
- else
- l4 ([&]{trace << n << " variable value mismatch forcing "
- << "update of " << t;});
+
// Fall through.
}
@@ -268,7 +306,35 @@ namespace build2
if (verb >= 2)
text << program_ << ' ' << ip << " >" << tp;
else if (verb)
- text << program_ << ' ' << ip;
+ {
+ // If we straight print the target, in most cases we will end up with
+ // something ugly like in{version...h.in} (due to the in{} target
+ // type search semantics). There is the `...h` part but also the
+ // `.in` part that is redundant given in{}. So let's tidy this up
+ // a bit if the extension could have been derived by in_search().
+ //
+ target_key ik (i.key ());
+
+ if (ik.ext)
+ {
+ string& ie (*ik.ext);
+ const string* te (t.ext ());
+
+ size_t in (ie.size ());
+ size_t tn (te != nullptr ? te->size () : 0);
+
+ if (in == tn + (tn != 0 ? 1 : 0) + 2) // [<te>.]in
+ {
+ if (ie.compare (in - 2, 2, "in") == 0)
+ {
+ if (tn == 0 || (ie.compare (0, tn, *te) == 0 && ie[tn] == '.'))
+ ie.clear ();
+ }
+ }
+ }
+
+ print_diag (program_.c_str (), move (ik), t);
+ }
// Read and process the file, one line at a time, while updating depdb.
//
@@ -313,7 +379,7 @@ namespace build2
#endif
auto_rmfile arm (tp);
- // Note: this default will only be used if the file if empty (i.e.,
+ // Note: this default will only be used if the file is empty (i.e.,
// does not contain even a newline).
//
const char* nl (
@@ -324,8 +390,8 @@ namespace build2
#endif
);
- string s; // Reuse the buffer.
- for (size_t ln (1);; ++ln)
+ uint64_t ln (1);
+ for (string s;; ++ln)
{
what = "read"; whom = &ip;
if (!getline (ifs, s))
@@ -338,97 +404,31 @@ namespace build2
if (crlf)
s.pop_back();
- // Not tracking column for now (see also depdb above).
- //
- const location l (ip, ln);
-
- // Scan the line looking for substiutions in the $<name>$ form. In
- // the strict mode treat $$ as an escape sequence.
- //
- for (size_t b (0), n, d; b != (n = s.size ()); b += d)
- {
- d = 1;
-
- if (s[b] != sym)
- continue;
-
- // Note that in the lax mode these should still be substitutions:
- //
- // @project@@
- // @@project@
-
- // Find the other end.
- //
- size_t e (b + 1);
- for (; e != (n = s.size ()); ++e)
- {
- if (s[e] == sym)
- {
- if (strict && e + 1 != n && s[e + 1] == sym) // Escape.
- s.erase (e, 1); // Keep one, erase the other.
- else
- break;
- }
- }
-
- if (e == n)
- {
- if (strict)
- fail (l) << "unterminated '" << sym << "'" << endf;
-
- break;
- }
-
- if (e - b == 1) // Escape (or just double symbol in the lax mode).
- {
- if (strict)
- s.erase (b, 1); // Keep one, erase the other.
+ what = "write"; whom = &tp;
+ if (ln != 1)
+ ofs << nl;
- continue;
- }
+ nl = crlf ? "\r\n" : "\n"; // Preserve the original line ending.
- // We have a (potential, in the lax mode) substition with b
- // pointing to the opening symbol and e -- to the closing.
- //
- string name (s, b + 1, e - b -1);
- if (optional<string> val =
- substitute (l, a, t, name, strict, null))
- {
- // Save in depdb.
- //
- if (dd_skip == 0)
- {
- // The line format is:
- //
- // <ln> <name> <hash>
- //
- string s (to_string (ln));
- s += ' ';
- s += name;
- s += ' ';
- s += sha256 (*val).string ();
- dd.write (s);
- }
- else
- --dd_skip;
+ if (ln == 1)
+ perform_update_pre (a, t, ofs, nl);
- // Patch the result in and adjust the delta.
- //
- s.replace (b, e - b + 1, *val);
- d = val->size ();
- }
- else
- d = e - b + 1; // Ignore this substitution.
- }
+ // Not tracking column for now (see also depdb above).
+ //
+ process (location (ip, ln),
+ a, t,
+ dd, dd_skip,
+ s, 0,
+ nl, sym, strict, smap, null);
- what = "write"; whom = &tp;
- if (ln != 1)
- ofs << nl; // See below.
ofs << s;
-
- nl = crlf ? "\r\n" : "\n"; // Preserve the original line ending.
}
+ what = "write"; whom = &tp;
+ if (ln == 1)
+ perform_update_pre (a, t, ofs, nl);
+ perform_update_post (a, t, ofs, nl);
+
// Close depdb before closing the output file so its mtime is not
// newer than of the output.
//
@@ -462,56 +462,147 @@ namespace build2
return prerequisite_target (&build2::search (t, p), i);
}
- string rule::
- lookup (const location& loc,
- action,
- const target& t,
- const string& n,
- const optional<string>& null) const
+ void rule::
+ perform_update_depdb (action, const target&, depdb&) const
{
- auto l (t[n]);
+ }
- if (l.defined ())
+ void rule::
+ perform_update_pre (action, const target&, ofdstream&, const char*) const
+ {
+ }
+
+ void rule::
+ perform_update_post (action, const target&, ofdstream&, const char*) const
+ {
+ }
+
+ void rule::
+ process (const location& l,
+ action a, const target& t,
+ depdb& dd, size_t& dd_skip,
+ string& s, size_t b,
+ const char* nl,
+ char sym,
+ bool strict,
+ const substitution_map* smap,
+ const optional<string>& null) const
+ {
+ // Scan the line looking for substiutions in the $<name>$ form. In the
+ // strict mode treat $$ as an escape sequence.
+ //
+ for (size_t n, d; b != (n = s.size ()); b += d)
{
- value v (*l);
+ d = 1;
- if (v.null)
+ if (s[b] != sym)
+ continue;
+
+ // Note that in the lax mode these should still be substitutions:
+ //
+ // @project@@
+ // @@project@
+
+ // Find the other end.
+ //
+ size_t e (b + 1);
+ for (; e != (n = s.size ()); ++e)
{
- if (null)
- return *null;
- else
- fail (loc) << "null value in variable '" << n << "'" <<
- info << "use in.null to specify null value substiution string";
+ if (s[e] == sym)
+ {
+ if (strict && e + 1 != n && s[e + 1] == sym) // Escape.
+ s.erase (e, 1); // Keep one, erase the other.
+ else
+ break;
+ }
}
- // For typed values call string() for conversion.
+ if (e == n)
+ {
+ if (strict)
+ fail (l) << "unterminated '" << sym << "'";
+
+ break;
+ }
+
+ if (e - b == 1) // Escape (or just double symbol in the lax mode).
+ {
+ if (strict)
+ s.erase (b, 1); // Keep one, erase the other.
+
+ continue;
+ }
+
+ // We have a (potential, in the lax mode) substition with b pointing
+ // to the opening symbol and e -- to the closing.
//
- try
+ if (optional<string> val = substitute (l,
+ a, t,
+ dd, dd_skip,
+ string (s, b + 1, e - b -1),
+ nullopt /* flags */,
+ strict, smap, null))
{
- return convert<string> (
- v.type == nullptr
- ? move (v)
- : t.ctx.functions.call (&t.base_scope (),
- "string",
- vector_view<value> (&v, 1),
- loc));
+ replace_newlines (*val, nl);
+
+ // Patch the result in and adjust the delta.
+ //
+ s.replace (b, e - b + 1, *val);
+ d = val->size ();
}
- catch (const invalid_argument& e)
+ else
+ d = e - b + 1; // Ignore this substitution.
+ }
+ }
+
+ optional<string> rule::
+ substitute (const location& l,
+ action a, const target& t,
+ depdb& dd, size_t& dd_skip,
+ const string& n,
+ optional<uint64_t> flags,
+ bool strict,
+ const substitution_map* smap,
+ const optional<string>& null) const
+ {
+ optional<string> val (substitute (l, a, t, n, flags, strict, smap, null));
+
+ if (val)
+ {
+ // Save in depdb.
+ //
+ if (dd_skip == 0)
{
- fail (loc) << e <<
- info << "while substituting '" << n << "'" << endf;
+ // The line format is:
+ //
+ // <ln> <name> <hash>[/<flags>]
+ //
+ string s (to_string (l.line));
+ s += ' ';
+ s += n;
+ s += ' ';
+ s += sha256 (*val).string ();
+ if (flags)
+ {
+ s += '/';
+ s += to_string (*flags);
+ }
+ dd.write (s);
}
+ else
+ --dd_skip;
}
- else
- fail (loc) << "undefined variable '" << n << "'" << endf;
+
+ return val;
}
optional<string> rule::
substitute (const location& l,
- action a,
- const target& t,
+ action a, const target& t,
const string& n,
+ optional<uint64_t> flags,
bool strict,
+ const substitution_map* smap,
const optional<string>& null) const
{
// In the lax mode scan the fragment to make sure it is a variable name
@@ -536,7 +627,75 @@ namespace build2
}
}
- return lookup (l, a, t, n, null);
+ return lookup (l, a, t, n, flags, smap, null);
+ }
+
+ string rule::
+ lookup (const location& loc,
+ action, const target& t,
+ const string& n,
+ optional<uint64_t> flags,
+ const substitution_map* smap,
+ const optional<string>& null) const
+ {
+ assert (!flags);
+
+ // First look in the substitution map.
+ //
+ if (smap != nullptr)
+ {
+ auto i (smap->find (n));
+
+ if (i != smap->end ())
+ {
+ if (i->second)
+ return *i->second;
+
+ if (null)
+ return *null;
+
+ fail (loc) << "null value in substitution map entry '" << n << "'" <<
+ info << "use in.null to specify null value substiution string";
+ }
+ }
+
+ // Next look for the buildfile variable.
+ //
+ auto l (t[n]);
+
+ if (l.defined ())
+ {
+ value v (*l);
+
+ if (v.null)
+ {
+ if (null)
+ return *null;
+
+ fail (loc) << "null value in variable '" << n << "'" <<
+ info << "use in.null to specify null value substiution string";
+ }
+
+ // For typed values call string() for conversion.
+ //
+ try
+ {
+ return convert<string> (
+ v.type == nullptr
+ ? move (v)
+ : t.ctx.functions.call (&t.base_scope (),
+ "string",
+ vector_view<value> (&v, 1),
+ loc));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (loc) << e <<
+ info << "while substituting '" << n << "'" << endf;
+ }
+ }
+ else
+ fail (loc) << "undefined variable '" << n << "'" << endf;
}
}
}