diff options
Diffstat (limited to 'libbuild2/in')
-rw-r--r-- | libbuild2/in/init.cxx | 27 | ||||
-rw-r--r-- | libbuild2/in/rule.cxx | 449 | ||||
-rw-r--r-- | libbuild2/in/rule.hxx | 97 | ||||
-rw-r--r-- | libbuild2/in/target.cxx | 22 | ||||
-rw-r--r-- | libbuild2/in/target.hxx | 7 |
5 files changed, 436 insertions, 166 deletions
diff --git a/libbuild2/in/init.cxx b/libbuild2/in/init.cxx index 18071f8..2fb73e1 100644 --- a/libbuild2/in/init.cxx +++ b/libbuild2/in/init.cxx @@ -34,7 +34,10 @@ namespace build2 // Enter variables. // { - auto& vp (rs.var_pool ()); + // All the variables we enter are qualified so go straight for the + // public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); // Alternative variable substitution symbol with '$' being the // default. @@ -58,7 +61,27 @@ namespace build2 // is still stricter than the autoconf's semantics which also leaves // unknown substitutions as is. // - vp.insert<string> ("in.substitution"); + const variable& im (vp.insert<string> ("in.mode")); + + // Original name of this variable for backwards compatibility. + // + vp.insert_alias (im, "in.substitution"); + + // Substitution map. Substitutions can be specified as key-value pairs + // rather than buildfile variables. This map is checked before the + // variables. An absent value in key-value has the NULL semantics. + // + // This mechanism has two primary uses: Firstly, it allows us to have + // substitution names that cannot be specified as buildfile variables. + // For example, a name may start with an underscore and thus be + // reserved or it may refer to one of the predefined variables such a + // `include` or `extension` that may have a wrong visibility and/or + // type. + // + // Secondly, this mechanism allows us to encapsulate a group of + // substitutions and pass this group around as a single value. + // + vp.insert<map<string, optional<string>>> ("in.substitutions"); // Fallback value to use for NULL value substitutions. If unspecified, // NULL substitutions are an error. 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; } } } diff --git a/libbuild2/in/rule.hxx b/libbuild2/in/rule.hxx index 2fa1305..67c2509 100644 --- a/libbuild2/in/rule.hxx +++ b/libbuild2/in/rule.hxx @@ -5,6 +5,7 @@ #define LIBBUILD2_IN_RULE_HXX #include <libbuild2/types.hxx> +#include <libbuild2/forward.hxx> // depdb #include <libbuild2/utility.hxx> #include <libbuild2/rule.hxx> @@ -17,8 +18,14 @@ namespace build2 { // Preprocess an .in file. // - // Note that a derived rule can use the target data pad to cache data - // (e.g., in match()) to be used in substitute/lookup() calls. + // Note that a derived rule can use the target auxiliary data storage to + // cache data (e.g., in match() or apply()) to be used in substitute() and + // lookup() calls. + // + // A derived rule is also required to derive the target file name in + // match() instead of apply() to make it available early for the in{} + // prerequisite search (see install::file_rule::apply_impl() for + // background). // // Note also that currently this rule ignores the dry-run mode (see // perform_update() for the rationale). @@ -42,7 +49,7 @@ namespace build2 null_ (move (null)) {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual recipe apply (action, target&) const override; @@ -50,24 +57,45 @@ namespace build2 virtual target_state perform_update (action, const target&) const; - // Customization hooks. + // Customization hooks and helpers. // + using substitution_map = map<string, optional<string>>; // Perform prerequisite search. // virtual prerequisite_target - search (action, - const target&, + search (action, const target&, const prerequisite_member&, include_type) const; + // Additional depdb entries. + // + virtual void + perform_update_depdb (action, const target&, depdb&) const; + + // Pre/post update. + // + virtual void + perform_update_pre (action, const target&, + ofdstream&, const char* newline) const; + + virtual void + perform_update_post (action, const target&, + ofdstream&, const char* newline) const; + // Perform variable lookup. // + // Flags can be used by a custom implementation to alter the lookup + // semantics, for example, for special substitutions. Note, however, + // that one must make sure this semantics cannot change without changes + // to the .in file (see the depdb logic for details). + // virtual string lookup (const location&, - action, - const target&, + action, const target&, const string& name, + optional<uint64_t> flags, + const substitution_map*, const optional<string>& null) const; // Perform variable substitution. Return nullopt if it should be @@ -75,12 +103,61 @@ namespace build2 // virtual optional<string> substitute (const location&, - action, - const target&, + action, const target&, const string& name, + optional<uint64_t> flags, bool strict, + const substitution_map*, const optional<string>& null) const; + // Call the above version and do any necessary depdb saving. + // + optional<string> + substitute (const location&, + action, const target&, + depdb& dd, size_t& dd_skip, + const string& name, + optional<uint64_t> flags, + bool strict, + const substitution_map*, + const optional<string>& null) const; + + // Process a line of input from the specified position performing any + // necessary substitutions. + // + virtual void + process (const location&, + action, const target&, + depdb& dd, size_t& dd_skip, + string& line, size_t pos, + const char* newline, + char sym, + bool strict, + const substitution_map*, + const optional<string>& null) const; + + // Replace newlines in a multi-line value with the given newline + // sequence. + // + static void + replace_newlines (string& v, const char* newline) + { + for (size_t p (0), n; (p = v.find ('\n', p)) != string::npos; p += n) + { + n = 1; + + // Deal with CRLF in the value. + // + if (p != 0 && v[p - 1] == '\r') + { + --p; + ++n; + } + + v.replace (p, n, newline); + } + } + protected: const string rule_id_; const string program_; diff --git a/libbuild2/in/target.cxx b/libbuild2/in/target.cxx index d9bc8a7..d664e3a 100644 --- a/libbuild2/in/target.cxx +++ b/libbuild2/in/target.cxx @@ -10,7 +10,7 @@ namespace build2 namespace in { static const target* - in_search (const target& xt, const prerequisite_key& cpk) + in_search (context& ctx, const target* xt, const prerequisite_key& cpk) { // If we have no extension then derive it from our target. Then delegate // to file_search(). @@ -18,18 +18,26 @@ namespace build2 prerequisite_key pk (cpk); optional<string>& e (pk.tk.ext); - if (!e) + if (!e && xt != nullptr) { - if (const file* t = xt.is_a<file> ()) + // Why is the extension, say, .h.in and not .in (with .h being in the + // name)? While this is mostly academic (in this case things will work + // the same either way), conceptually, it is a header template rather + // than some file template. In other words, we are adding the second + // level classification. + // + // See also the low verbosity tidying up code in the rule. + // + if (const file* t = xt->is_a<file> ()) { const string& te (t->derive_extension ()); e = te + (te.empty () ? "" : ".") + "in"; } else - fail << "prerequisite " << pk << " for a non-file target " << xt; + fail << "prerequisite " << pk << " for a non-file target " << *xt; } - return file_search (xt, pk); + return file_search (ctx, xt, pk); } static bool @@ -51,9 +59,9 @@ namespace build2 &target_extension_none, nullptr, /* default_extension */ // Taken care of by search. &in_pattern, - &target_print_1_ext_verb, // Same as file. + &target_print_1_ext_verb, // Same as file (but see rule). &in_search, - false + target_type::flag::none }; } } diff --git a/libbuild2/in/target.hxx b/libbuild2/in/target.hxx index 20a0c44..619c06e 100644 --- a/libbuild2/in/target.hxx +++ b/libbuild2/in/target.hxx @@ -35,11 +35,14 @@ namespace build2 class LIBBUILD2_IN_SYMEXPORT in: public file { public: - using file::file; + in (context& c, dir_path d, dir_path o, string n) + : file (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } public: static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} }; } } |