aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-07-17 13:18:17 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-07-17 13:18:17 +0200
commitfe6f3ec0868185511f5acefb2729eb879798f052 (patch)
tree63a44a73be76ebbdc0be95c72b85756a6442c4eb
parent3a739d4fe0aa7ed1786a93a20c7f43a69b0ebd16 (diff)
Reimplement version::in_rule in terms of in::rule
Significantly, the version::in_rule rule now track changes to the substitution values.
-rw-r--r--build2/in/init.cxx2
-rw-r--r--build2/in/rule.cxx11
-rw-r--r--build2/in/rule.hxx19
-rw-r--r--build2/version/init.cxx13
-rw-r--r--build2/version/module.hxx11
-rw-r--r--build2/version/rule.cxx552
-rw-r--r--build2/version/rule.hxx15
7 files changed, 182 insertions, 441 deletions
diff --git a/build2/in/init.cxx b/build2/in/init.cxx
index 337722a..a87f8b3 100644
--- a/build2/in/init.cxx
+++ b/build2/in/init.cxx
@@ -18,7 +18,7 @@ namespace build2
{
namespace in
{
- static const rule rule_;
+ static const rule rule_ ("in", "in");
bool
base_init (scope& rs,
diff --git a/build2/in/rule.cxx b/build2/in/rule.cxx
index de43e4d..6be2e71 100644
--- a/build2/in/rule.cxx
+++ b/build2/in/rule.cxx
@@ -55,7 +55,7 @@ namespace build2
// Derive the file name.
//
- // If this is an executable with an uspecified extension, then default
+ // If this is an executable with an unspecified extension, then default
// to no extension (i.e., a shell script).
//
t.derive_path (t.is_a<exe> () ? "" : nullptr);
@@ -187,7 +187,7 @@ namespace build2
// First should come the rule name/version.
//
- if (dd.expect ("in 1") != nullptr)
+ if (dd.expect (rule_id_ + " 1") != nullptr)
l4 ([&]{trace << "rule mismatch forcing update of " << t;});
// Then the substitution symbol.
@@ -253,6 +253,9 @@ namespace build2
//
// <ln> <name> <hash>
//
+ // Note that <name> can contain spaces (see the constraint check
+ // expressions in the version module).
+ //
char* e (nullptr);
uint64_t ln (strtoull (s->c_str (), &e, 10));
@@ -296,9 +299,9 @@ namespace build2
}
if (verb >= 2)
- text << "in " << ip << " >" << tp;
+ text << program_ << ' ' << ip << " >" << tp;
else if (verb)
- text << "in " << ip;
+ text << program_ << ' ' << ip;
// Read and process the file, one line at a time.
//
diff --git a/build2/in/rule.hxx b/build2/in/rule.hxx
index a68e80c..17fbaed 100644
--- a/build2/in/rule.hxx
+++ b/build2/in/rule.hxx
@@ -16,11 +16,24 @@ 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.
+ //
class rule: public build2::rule
{
public:
- rule (char symbol = '$', bool strict = true)
- : symbol_ (symbol), strict_ (strict) {}
+ // The rule id is used to form the rule name/version entry in depdb. The
+ // program argument is the pseudo-program name to use in the command
+ // line diagnostics.
+ //
+ rule (string rule_id,
+ string program,
+ char symbol = '$',
+ bool strict = true)
+ : rule_id_ (move (rule_id)),
+ program_ (move (program)),
+ symbol_ (symbol),
+ strict_ (strict) {}
virtual bool
match (action, target&, const string&) const override;
@@ -46,6 +59,8 @@ namespace build2
perform_update (action, const target&) const;
protected:
+ const string rule_id_;
+ const string program_;
char symbol_;
bool strict_;
};
diff --git a/build2/version/init.cxx b/build2/version/init.cxx
index d2df772..397d7da 100644
--- a/build2/version/init.cxx
+++ b/build2/version/init.cxx
@@ -232,7 +232,11 @@ namespace build2
// Create the module.
//
- mod.reset (new module (move (v), committed, rewritten, move (ds)));
+ mod.reset (new module (cast<string> (rs.vars[var_project]),
+ move (v),
+ committed,
+ rewritten,
+ move (ds)));
return true; // Init first (dist.package, etc).
}
@@ -295,13 +299,6 @@ namespace build2
}
}
- // Enter variables.
- //
- {
- m.in_symbol = var_pool.find ("in.symbol"); // in.base
- m.in_substitution = var_pool.find ("in.substitution"); // in.base
- }
-
// Register rules.
//
{
diff --git a/build2/version/module.hxx b/build2/version/module.hxx
index b3bf813..56536c3 100644
--- a/build2/version/module.hxx
+++ b/build2/version/module.hxx
@@ -24,22 +24,23 @@ namespace build2
{
static const string name;
+ const string& project; // The project variable value.
+
butl::standard_version version;
bool committed; // Whether this is a committed snapshot.
bool rewritten; // Whether this is a rewritten .z snapshot.
dependency_constraints dependencies;
- const variable* in_symbol = nullptr; // in.symbol
- const variable* in_substitution = nullptr; // in.substitution
-
bool dist_uncommitted = false;
- module (butl::standard_version v,
+ module (const string& p,
+ butl::standard_version v,
bool c,
bool r,
dependency_constraints d)
- : version (move (v)),
+ : project (p),
+ version (move (v)),
committed (c),
rewritten (r),
dependencies (move (d)) {}
diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx
index 0250a15..a0ff1e2 100644
--- a/build2/version/rule.cxx
+++ b/build2/version/rule.cxx
@@ -77,160 +77,41 @@ namespace build2
if (!fi)
l4 ([&]{trace << "no in file prerequisite for target " << t;});
- return fm && fi;
- }
-
- recipe in_rule::
- apply (action a, target& xt) const
- {
- file& t (static_cast<file&> (xt));
-
- // Derive the file name.
- //
- t.derive_path ();
-
- // Inject dependency on the output directory.
- //
- inject_fsdir (a, t);
+ bool r (fm && fi);
- // Match prerequisite members.
+ // If we match, lookup and cache the module for the update operation.
//
- match_prerequisite_members (a, t);
+ if (r && a == perform_update_id)
+ t.data (rs.modules.lookup<module> (module::name));
- switch (a)
- {
- case perform_update_id: return &perform_update;
- case perform_clean_id: return &perform_clean_depdb; // Standard clean.
- default: return noop_recipe; // Configure update.
- }
+ return r;
}
- target_state in_rule::
- perform_update (action a, const target& xt)
+ string in_rule::
+ lookup (const location& l, const target& t, const string& n) const
{
- tracer trace ("version::in_rule::perform_update");
-
- const file& t (xt.as<const file&> ());
- const path& tp (t.path ());
-
- const scope& rs (t.root_scope ());
- const module& m (*rs.modules.lookup<module> (module::name));
-
- // The substitution symbol can be overridden with the in.symbol
- // variable.
+ // Note that this code will be executed during up-to-date check for each
+ // substitution so let's try not to do anything overly sub-optimal here.
//
- char sym ('$');
- if (const string* s = cast_null<string> (t[m.in_symbol]))
- {
- if (s->size () == 1)
- sym = s->front ();
- else
- fail << "invalid substitution symbol '" << *s << "'";
- }
+ const module& m (*t.data<const module*> ());
- // The substitution mode can be overridden with the in.substitution
- // variable.
+ // Split it into the package name and the variable/condition name.
//
- bool strict (true);
- if (const string* s = cast_null<string> (t[m.in_substitution]))
- {
- if (*s == "lax")
- strict = false;
- else if (*s != "strict")
- fail << "invalid substitution mode '" << *s << "'";
- }
-
- // Determine if anything needs to be updated.
+ // We used to bail if there is no package component but now we treat it
+ // the same as project. This can be useful when trying to reuse existing
+ // .in files (e.g., from autoconf, etc).
//
- timestamp mt (t.load_mtime ());
- auto pr (execute_prerequisites<in> (a, t, mt));
-
- bool update (!pr.first);
- target_state ts (update ? target_state::changed : *pr.first);
+ size_t p (n.find ('.'));
- const in& i (pr.second);
- const path& ip (i.path ());
-
- // We use depdb to track both the .in file and the potentially patched
- // snapshot.
- //
+ if (p == string::npos || n.compare (0, p, m.project) == 0)
{
- depdb dd (tp + ".d");
-
- // First should come the rule name/version.
- //
- if (dd.expect ("version.in 3") != nullptr)
- l4 ([&]{trace << "rule mismatch forcing update of " << t;});
-
- // Then the substitution symbol.
- //
- if (dd.expect (string (1, sym)) != nullptr)
- l4 ([&]{trace << "substitution symbol mismatch forcing update of"
- << t;});
-
- // Then the substitution mode.
- //
- if (dd.expect (strict ? "strict" : "lax") != nullptr)
- l4 ([&]{trace << "substitution mode mismatch forcing update of"
- << t;});
-
- // Then the .in file.
+ // Standard lookup.
//
- if (dd.expect (i.path ()) != nullptr)
- l4 ([&]{trace << "in file mismatch forcing update of " << t;});
-
- // Finally the snapshot info.
- //
- if (dd.expect (m.version.string_snapshot ()) != nullptr)
- l4 ([&]{trace << "snapshot mismatch forcing update of " << t;});
-
- // Update if depdb mismatch.
- //
- if (dd.writing () || dd.mtime () > mt)
- update = true;
-
- //@@ TODO: what if one of the substituted non-version values is
- // changes. See how we handle this in the .in module. In fact,
- // it feels like this should be an extended version of in.
-
- dd.close ();
+ return rule::lookup (l, t, p == string::npos ? n : string (n, p + 1));
}
- // If nothing changed, then we are done.
- //
- if (!update)
- return ts;
-
- const string& proj (cast<string> (rs.vars[var_project]));
-
- // Perform substitutions for the project itself (normally the version.*
- // variables but we allow anything set on the target and up).
- //
- auto subst_self = [&t] (const location& l, const string& s) -> string
- {
- if (lookup x = t[s])
- {
- value v (*x);
-
- // For typed values call the string() function to convert it.
- //
- try
- {
- return convert<string> (
- v.type == nullptr
- ? move (v)
- : functions.call (
- &t.base_scope (), "string", vector_view<value> (&v, 1), l));
- }
- catch (const invalid_argument& e)
- {
- fail (l) << e <<
- info << "while substituting '" << s << "'" << endf;
- }
- }
- else
- fail (l) << "undefined project variable '" << s << "'" << endf;
- };
+ string pn (n, 0, p);
+ string vn (n, p + 1);
// Perform substitutions for a dependency. Here we recognize the
// following substitutions:
@@ -243,319 +124,166 @@ namespace build2
// snapshot number macro (only needed if you plan to include snapshot
// informaton in your constraints).
//
- auto subst_dep = [&m] (const location& l,
- const string& n,
- const string& s)
- {
- // For now we re-parse the constraint every time. Firstly because
- // not all of them are necessarily in the standard form and secondly
- // because of the MT-safety.
- //
- standard_version_constraint c;
-
- try
- {
- auto i (m.dependencies.find (n));
+ // Note also that the last two (condition and check) can only be used in
+ // the strict substitution mode since in::rule::substitute() will skip
+ // them in the lax mode.
- if (i == m.dependencies.end ())
- fail (l) << "unknown dependency '" << n << "'";
-
- if (i->second.empty ())
- fail (l) << "no version constraint for dependency " << n;
+ // For now we re-parse the constraint every time. Firstly because not
+ // all of them are necessarily in the standard form and secondly because
+ // of the MT-safety.
+ //
+ standard_version_constraint c;
- c = standard_version_constraint (i->second);
- }
- catch (const invalid_argument& e)
- {
- fail (l) << "invalid version constraint for dependency " << n
- << ": " << e;
- }
+ try
+ {
+ auto i (m.dependencies.find (pn));
- // Now substitute.
- //
- size_t i;
- if (s == "version")
- {
- return c.string (); // Use normalized representation.
- }
- if (s.compare (0, (i = 6), "check(") == 0 ||
- s.compare (0, (i = 10), "condition(") == 0)
- {
- size_t j (s.find_first_of (",)", i));
+ if (i == m.dependencies.end ())
+ fail (l) << "unknown dependency '" << pn << "'";
- if (j == string::npos || (s[j] == ',' && s.back () != ')'))
- fail (l) << "missing closing ')'";
+ if (i->second.empty ())
+ fail (l) << "no version constraint for dependency " << pn;
- string vm (s, i, j - i); // VER macro.
- string sm (s[j] == ',' // SNAP macro.
- ? string (s, j + 1, s.size () - j - 2)
- : string ());
+ c = standard_version_constraint (i->second);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version constraint for dependency " << pn
+ << ": " << e;
+ }
- trim (vm);
- trim (sm);
+ // Now substitute.
+ //
+ size_t i;
+ if (vn == "version")
+ {
+ return c.string (); // Use normalized representation.
+ }
+ if (vn.compare (0, (i = 6), "check(") == 0 ||
+ vn.compare (0, (i = 10), "condition(") == 0)
+ {
+ size_t j (vn.find_first_of (",)", i));
- auto cond = [&l, &c, &vm, &sm] () -> string
- {
- auto& miv (c.min_version);
- auto& mav (c.max_version);
+ if (j == string::npos || (vn[j] == ',' && vn.back () != ')'))
+ fail (l) << "missing closing ')'";
- bool mio (c.min_open);
- bool mao (c.max_open);
+ string vm (vn, i, j - i); // VER macro.
+ string sm (vn[j] == ',' // SNAP macro.
+ ? string (vn, j + 1, vn.size () - j - 2)
+ : string ());
- if (sm.empty () &&
- ((miv && miv->snapshot ()) ||
- (mav && mav->snapshot ())))
- fail (l) << "snapshot macro required for " << c.string ();
+ trim (vm);
+ trim (sm);
- auto cmp = [] (const string& m, const char* o, uint64_t v)
- {
- return m + o + to_string (v) + "ULL";
- };
-
- // Note that version orders everything among pre-releases (that
- // E being 0/1). So the snapshot comparison is only necessary
- // "inside" the same pre-release.
- //
- auto max_cmp = [&vm, &sm, mao, &mav, &cmp] (bool p = false)
- {
- string r;
+ auto cond = [&l, &c, &vm, &sm] () -> string
+ {
+ auto& miv (c.min_version);
+ auto& mav (c.max_version);
- if (mav->snapshot ())
- {
- r += (p ? "(" : "");
+ bool mio (c.min_open);
+ bool mao (c.max_open);
- r += cmp (vm, " < ", mav->version) + " || (";
- r += cmp (vm, " == ", mav->version) + " && ";
- r += cmp (sm, (mao ? " < " : " <= "), mav->snapshot_sn) + ")";
+ if (sm.empty () &&
+ ((miv && miv->snapshot ()) ||
+ (mav && mav->snapshot ())))
+ fail (l) << "snapshot macro required for " << c.string ();
- r += (p ? ")" : "");
- }
- else
- r = cmp (vm, (mao ? " < " : " <= "), mav->version);
+ auto cmp = [] (const string& m, const char* o, uint64_t v)
+ {
+ return m + o + to_string (v) + "ULL";
+ };
- return r;
- };
+ // Note that version orders everything among pre-releases (that E
+ // being 0/1). So the snapshot comparison is only necessary "inside"
+ // the same pre-release.
+ //
+ auto max_cmp = [&vm, &sm, mao, &mav, &cmp] (bool p = false)
+ {
+ string r;
- auto min_cmp = [&vm, &sm, mio, &miv, &cmp] (bool p = false)
+ if (mav->snapshot ())
{
- string r;
-
- if (miv->snapshot ())
- {
- r += (p ? "(" : "");
+ r += (p ? "(" : "");
- r += cmp (vm, " > ", miv->version) + " || (";
- r += cmp (vm, " == ", miv->version) + " && ";
- r += cmp (sm, (mio ? " > " : " >= "), miv->snapshot_sn) + ")";
+ r += cmp (vm, " < ", mav->version) + " || (";
+ r += cmp (vm, " == ", mav->version) + " && ";
+ r += cmp (sm, (mao ? " < " : " <= "), mav->snapshot_sn) + ")";
- r += (p ? ")" : "");
- }
- else
- r = cmp (vm, (mio ? " > " : " >= "), miv->version);
-
- return r;
- };
+ r += (p ? ")" : "");
+ }
+ else
+ r = cmp (vm, (mao ? " < " : " <= "), mav->version);
- // < / <=
- //
- if (!miv)
- return max_cmp ();
+ return r;
+ };
- // > / >=
- //
- if (!mav)
- return min_cmp ();
+ auto min_cmp = [&vm, &sm, mio, &miv, &cmp] (bool p = false)
+ {
+ string r;
- // ==
- //
- if (*miv == *mav)
+ if (miv->snapshot ())
{
- string r (cmp (vm, " == ", miv->version));
+ r += (p ? "(" : "");
- if (miv->snapshot ())
- r += " && " + cmp (sm, " == ", miv->snapshot_sn);
+ r += cmp (vm, " > ", miv->version) + " || (";
+ r += cmp (vm, " == ", miv->version) + " && ";
+ r += cmp (sm, (mio ? " > " : " >= "), miv->snapshot_sn) + ")";
- return r;
+ r += (p ? ")" : "");
}
+ else
+ r = cmp (vm, (mio ? " > " : " >= "), miv->version);
- // range
- //
- return min_cmp (true) + " && " + max_cmp (true);
+ return r;
};
- if (s[1] == 'o') // condition
- return cond ();
-
- string r;
-
- // This is tricky: if the version header hasn't been generated yet,
- // then the check will fail. Maybe a better solution is to disable
- // diagnostics and ignore (some) errors during dependency
- // extraction.
+ // < / <=
//
- r += "#ifdef " + vm + "\n";
- r += "# if !(" + cond () + ")\n";
- r += "# error incompatible " + n + " version, ";
- r += n + ' ' + c.string () + " is required\n";
- r += "# endif\n";
- r += "#endif";
-
- return r;
- }
- else
- fail (l) << "unknown dependency substitution '" << s << "'" << endf;
- };
-
- if (verb >= 2)
- text << "ver " << ip << " >" << tp;
- else if (verb)
- text << "ver " << ip;
-
- // Read and process the file, one line at a time.
- //
- const char* what;
- const path* whom;
- try
- {
- what = "open"; whom = &ip;
- ifdstream ifs (ip, fdopen_mode::in, ifdstream::badbit);
-
- what = "open"; whom = &tp;
- ofdstream ofs (tp);
- auto_rmfile arm (tp);
+ if (!miv)
+ return max_cmp ();
- string s; // Reuse the buffer.
- for (size_t ln (1);; ++ln)
- {
- what = "read"; whom = &ip;
- if (!getline (ifs, s))
- break; // Could not read anything, not even newline.
-
- const location l (&ip, ln); // Not tracking column for now.
+ // > / >=
+ //
+ if (!mav)
+ return min_cmp ();
- // Scan the line looking for substiutions in the $<pkg>.<rest>$
- // form. In the strict mode treat $$ as an escape sequence.
+ // ==
//
- for (size_t b (0), n, d; b != (n = s.size ()); b += d)
+ if (*miv == *mav)
{
- d = 1;
-
- if (s[b] != sym)
- continue;
-
- // Note that in the lax mode these should still be substitutions:
- //
- // @project@@
- // @@project@
+ string r (cmp (vm, " == ", miv->version));
- // 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;
+ if (miv->snapshot ())
+ r += " && " + cmp (sm, " == ", miv->snapshot_sn);
- 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.
- //
- if (!strict)
- {
- // Scan the fragment to make sure it is a variable name (that
- // is, it can be expanded as just $<name>; see lexer's variable
- // mode for details).
- //
- size_t i;
- for (i = b + 1; i != e; )
- {
- bool f (i == b + 1); // First.
- char c (s[i++]);
- bool l (i == e); // Last.
-
- if (c == '_' || (f ? alpha (c) : alnum (c)))
- continue;
-
- if (c == '.' && !l)
- continue;
-
- i = string::npos;
- break;
- }
-
- if (i == string::npos)
- {
- d = e - b + 1; // Ignore this substitution.
- continue;
- }
- }
-
- // Split it into the package name and the trailer.
- //
- // We used to bail if there is no package component but now we
- // treat it the same as project. This can be useful when trying to
- // reuse existing .in files (e.g., from autoconf, etc).
- //
- string sn, st;
- size_t p (s.find ('.', b + 1));
-
- if (p != string::npos && p < e)
- {
- sn.assign (s, b + 1, p - b - 1);
- st.assign (s, p + 1, e - p - 1);
- }
- else
- st.assign (s, b + 1, e - b - 1);
-
- string sr (sn.empty () || sn == proj
- ? subst_self (l, st)
- : subst_dep (l, sn, st));
-
- // Patch the result in and adjust the delta.
- //
- s.replace (b, e - b + 1, sr);
- d = sr.size ();
+ return r;
}
- what = "write"; whom = &tp;
- ofs << s << endl;
- }
+ // range
+ //
+ return min_cmp (true) + " && " + max_cmp (true);
+ };
- what = "close"; whom = &tp;
- ofs.close ();
- arm.cancel ();
+ if (vn[1] == 'o') // condition
+ return cond ();
- what = "close"; whom = &ip;
- ifs.close ();
- }
- catch (const io_error& e)
- {
- fail << "unable to " << what << ' ' << *whom << ": " << e;
- }
+ string r;
- t.mtime (system_clock::now ());
- return target_state::changed;
+ // This is tricky: if the version header hasn't been generated yet,
+ // then the check will fail. Maybe a better solution is to disable
+ // diagnostics and ignore (some) errors during dependency extraction.
+ //
+ r += "#ifdef " + vm + "\n";
+ r += "# if !(" + cond () + ")\n";
+ r += "# error incompatible " + pn + " version, ";
+ r += pn + ' ' + c.string () + " is required\n";
+ r += "# endif\n";
+ r += "#endif";
+
+ return r;
+ }
+ else
+ fail (l) << "unknown dependency substitution '" << vn << "'" << endf;
}
// manifest_install_rule
diff --git a/build2/version/rule.hxx b/build2/version/rule.hxx
index b01383f..afed11a 100644
--- a/build2/version/rule.hxx
+++ b/build2/version/rule.hxx
@@ -8,28 +8,25 @@
#include <build2/types.hxx>
#include <build2/utility.hxx>
-#include <build2/rule.hxx>
+#include <build2/in/rule.hxx>
#include <build2/install/rule.hxx>
namespace build2
{
namespace version
{
- // Preprocess an .in file.
+ // Preprocess an .in file that depends on manifest.
//
- class in_rule: public rule
+ class in_rule: public in::rule
{
public:
- in_rule () {}
+ in_rule (): rule ("version.in 1", "ver") {}
virtual bool
match (action, target&, const string&) const override;
- virtual recipe
- apply (action, target&) const override;
-
- static target_state
- perform_update (action, const target&);
+ virtual string
+ lookup (const location&, const target&, const string&) const override;
};
// Pre-process manifest before installation to patch in the version.