diff options
Diffstat (limited to 'libbuild2/in/rule.cxx')
-rw-r--r-- | libbuild2/in/rule.cxx | 449 |
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; } } } |