aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/version/rule.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-08-01 16:10:48 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-08-01 16:41:08 +0300
commitda9cbf29c403d27c2940f9b31199c4648f8ae4a1 (patch)
tree9732de6f48bdaead2becf32be41b1284e1e83e00 /libbuild2/version/rule.cxx
parent8e69e09b7ec68377758c63092f9b254e95a0d7be (diff)
Move version build system module to separate library
Diffstat (limited to 'libbuild2/version/rule.cxx')
-rw-r--r--libbuild2/version/rule.cxx334
1 files changed, 334 insertions, 0 deletions
diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx
new file mode 100644
index 0000000..37e6b0f
--- /dev/null
+++ b/libbuild2/version/rule.cxx
@@ -0,0 +1,334 @@
+// file : libbuild2/version/rule.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/version/rule.hxx>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/diagnostics.hxx>
+
+#include <libbuild2/in/target.hxx>
+
+#include <libbuild2/version/module.hxx>
+#include <libbuild2/version/utility.hxx>
+
+using namespace std;
+using namespace butl;
+
+namespace build2
+{
+ namespace version
+ {
+ using in::in;
+
+ // Return true if this prerequisite is a project's manifest file. To be
+ // sure we would need to search it into target but that we can't do in
+ // match().
+ //
+ static inline bool
+ manifest_prerequisite (const scope& rs, const prerequisite_member& p)
+ {
+ if (!p.is_a<manifest> () || p.name () != "manifest")
+ return false;
+
+ const scope& s (p.scope ());
+
+ if (s.root_scope () == nullptr) // Out of project prerequisite.
+ return false;
+
+ dir_path d (p.dir ());
+ if (d.relative ())
+ d = s.src_path () / d;
+ d.normalize ();
+
+ return d == rs.src_path ();
+ }
+
+ // in_rule
+ //
+ bool in_rule::
+ match (action a, target& xt, const string&) const
+ {
+ tracer trace ("version::in_rule::match");
+
+ file& t (static_cast<file&> (xt));
+ const scope& rs (t.root_scope ());
+
+ bool fm (false); // Found manifest.
+ bool fi (false); // Found in.
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (include (a, t, p) != include_type::normal) // Excluded/ad hoc.
+ continue;
+
+ fm = fm || manifest_prerequisite (rs, p);
+ fi = fi || p.is_a<in> ();
+ }
+
+ // Note that while normally we print these at verbosity level 4, these
+ // ones get quite noisy since we try this rule any file target.
+ //
+ if (!fm)
+ l5 ([&]{trace << "no manifest prerequisite for target " << t;});
+
+ if (!fi)
+ l5 ([&]{trace << "no in file prerequisite for target " << t;});
+
+ bool r (fm && fi);
+
+ // If we match, lookup and cache the module for the update operation.
+ //
+ if (r && a == perform_update_id)
+ t.data (rs.lookup_module<module> (module::name));
+
+ return r;
+ }
+
+ string in_rule::
+ lookup (const location& l,
+ action a,
+ const target& t,
+ const string& n) const
+ {
+ // 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.
+ //
+ const module& m (*t.data<const module*> ());
+
+ // Split it into the package name and the variable/condition name.
+ //
+ // 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).
+ //
+ size_t p (n.find ('.'));
+
+ if (p == string::npos || n.compare (0, p, m.project) == 0)
+ {
+ return rule::lookup (l, // Standard lookup.
+ a,
+ t,
+ p == string::npos ? n : string (n, p + 1));
+ }
+
+ string pn (n, 0, p);
+ string vn (n, p + 1);
+
+ // Perform substitutions for a dependency. Here we recognize the
+ // following substitutions:
+ //
+ // $libfoo.version$ - textual version constraint.
+ // $libfoo.condition(VER[,SNAP])$ - numeric satisfaction condition.
+ // $libfoo.check(VER[,SNAP])$ - numeric satisfaction check (#if ...).
+ //
+ // Where VER is the version number macro and SNAP is the optional
+ // snapshot number macro (only needed if you plan to include snapshot
+ // informaton in your constraints).
+ //
+ // 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.
+
+ // 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 dc;
+ const package_name* dn;
+ {
+ auto i (m.dependencies.find (pn));
+
+ if (i == m.dependencies.end ())
+ fail (l) << "unknown dependency '" << pn << "'";
+
+ const dependency& dp (i->second);
+
+ if (dp.constraint.empty ())
+ fail (l) << "no version constraint for dependency " << dp.name;
+
+ try
+ {
+ dc = standard_version_constraint (dp.constraint, m.version);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version constraint for dependency " << dp.name
+ << " " << dp.constraint << ": " << e;
+ }
+
+ dn = &dp.name;
+ }
+
+ // Now substitute.
+ //
+ size_t i;
+ if (vn == "version")
+ {
+ return dc.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));
+
+ if (j == string::npos || (vn[j] == ',' && vn.back () != ')'))
+ fail (l) << "missing closing ')'";
+
+ string vm (vn, i, j - i); // VER macro.
+ string sm (vn[j] == ',' // SNAP macro.
+ ? string (vn, j + 1, vn.size () - j - 2)
+ : string ());
+
+ trim (vm);
+ trim (sm);
+
+ auto cond = [&l, &dc, &vm, &sm] () -> string
+ {
+ auto& miv (dc.min_version);
+ auto& mav (dc.max_version);
+
+ bool mio (dc.min_open);
+ bool mao (dc.max_open);
+
+ if (sm.empty () &&
+ ((miv && miv->snapshot ()) ||
+ (mav && mav->snapshot ())))
+ fail (l) << "snapshot macro required for " << dc.string ();
+
+ 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;
+
+ if (mav->snapshot ())
+ {
+ r += (p ? "(" : "");
+
+ r += cmp (vm, " < ", mav->version) + " || (";
+ r += cmp (vm, " == ", mav->version) + " && ";
+ r += cmp (sm, (mao ? " < " : " <= "), mav->snapshot_sn) + ")";
+
+ r += (p ? ")" : "");
+ }
+ else
+ r = cmp (vm, (mao ? " < " : " <= "), mav->version);
+
+ return r;
+ };
+
+ auto min_cmp = [&vm, &sm, mio, &miv, &cmp] (bool p = false)
+ {
+ string r;
+
+ if (miv->snapshot ())
+ {
+ r += (p ? "(" : "");
+
+ r += cmp (vm, " > ", miv->version) + " || (";
+ r += cmp (vm, " == ", miv->version) + " && ";
+ r += cmp (sm, (mio ? " > " : " >= "), miv->snapshot_sn) + ")";
+
+ r += (p ? ")" : "");
+ }
+ else
+ r = cmp (vm, (mio ? " > " : " >= "), miv->version);
+
+ return r;
+ };
+
+ // < / <=
+ //
+ if (!miv)
+ return max_cmp ();
+
+ // > / >=
+ //
+ if (!mav)
+ return min_cmp ();
+
+ // ==
+ //
+ if (*miv == *mav)
+ {
+ string r (cmp (vm, " == ", miv->version));
+
+ if (miv->snapshot ())
+ r += " && " + cmp (sm, " == ", miv->snapshot_sn);
+
+ return r;
+ }
+
+ // range
+ //
+ return min_cmp (true) + " && " + max_cmp (true);
+ };
+
+ if (vn[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 " + dn->string () + " version, ";
+ r += dn->string () + ' ' + dc.string () + " is required\n";
+ r += "# endif\n";
+ r += "#endif";
+
+ return r;
+ }
+ else
+ fail (l) << "unknown dependency substitution '" << vn << "'" << endf;
+ }
+
+ // manifest_install_rule
+ //
+ bool manifest_install_rule::
+ match (action a, target& t, const string&) const
+ {
+ // We only match project's manifest.
+ //
+ if (!t.is_a<manifest> () || t.name != "manifest")
+ return false;
+
+ // Must be in project's src_root.
+ //
+ const scope& s (t.base_scope ());
+ if (s.root_scope () != &s || s.src_path () != t.dir)
+ return false;
+
+ return file_rule::match (a, t, "");
+ }
+
+ auto_rmfile manifest_install_rule::
+ install_pre (const file& t, const install_dir&) const
+ {
+ const path& p (t.path ());
+
+ const scope& rs (t.root_scope ());
+ const module& m (*rs.lookup_module<module> (module::name));
+
+ if (!m.rewritten)
+ return auto_rmfile (p, false /* active */);
+
+ // Our options are to use path::temp_path() or to create a .t file in
+ // the out tree. Somehow the latter feels more appropriate (even though
+ // if we crash in between, we won't clean it up).
+ //
+ return fixup_manifest (p, rs.out_path () / "manifest.t", m.version);
+ }
+ }
+}