aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-04-28 08:33:42 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-04-28 08:33:42 +0200
commit593fd960891027b97567b2622ed4b6c16070ab36 (patch)
treef4cb383f8606fde6adff7ac9f90a20cc9ad59840
parentac402e1fef2c13b5860ca5223d764bbfdfb867e3 (diff)
Implement support for pre-processing version headers (or other files)
Also implement the build system version check.
-rw-r--r--build2/algorithm31
-rw-r--r--build2/algorithm.cxx7
-rw-r--r--build2/algorithm.ixx22
-rw-r--r--build2/cc/compile.cxx31
-rw-r--r--build2/cli/rule.cxx28
-rw-r--r--build2/target13
-rw-r--r--build2/utility8
-rw-r--r--build2/utility.cxx2
-rw-r--r--build2/version-impl2
-rw-r--r--build2/version/init.cxx78
-rw-r--r--build2/version/module12
-rw-r--r--build2/version/rule17
-rw-r--r--build2/version/rule.cxx474
13 files changed, 629 insertions, 96 deletions
diff --git a/build2/algorithm b/build2/algorithm
index da38f19..601d2ef 100644
--- a/build2/algorithm
+++ b/build2/algorithm
@@ -300,46 +300,47 @@ namespace build2
// which ones should be used for timestamp comparison. If the filter is
// NULL, then all the prerequisites are used.
//
- // Note that the return value is a pair with the second half indicating
- // whether any prerequisites were updated. This is used to handle the
- // situation where some prerequisites were updated but no update of the
- // target is necessary. In this case we still signal that the target was
- // (conceptually, but not physically) changed. This is important both to
- // propagate the fact that some work has been done and to also allow our
- // dependents to detect this case if they are up to something tricky (like
- // recursively linking liba{} prerequisites).
+ // Note that the return value is an optional target state. If the target
+ // needs updating, then the value absent. Otherwise it is the state that
+ // should be returned. This is used to handle the situation where some
+ // prerequisites were updated but no update of the target is necessary. In
+ // this case we still signal that the target was (conceptually, but not
+ // physically) changed. This is important both to propagate the fact that
+ // some work has been done and to also allow our dependents to detect this
+ // case if they are up to something tricky (like recursively linking liba{}
+ // prerequisites).
//
// Note that because we use mtime, this function should normally only be
// used in the perform_update action (which is straight).
//
using prerequisite_filter = function<bool (const target&)>;
- pair<bool, target_state>
+ optional<target_state>
execute_prerequisites (action, const target&,
const timestamp&,
const prerequisite_filter& = nullptr);
// Another version of the above that does two extra things for the caller:
// it determines whether the action needs to be executed on the target based
- // on the passed timestamp and, if so, finds a prerequisite of the specified
- // type (e.g., a source file). If there are multiple prerequisites of this
- // type, then the first is returned (this can become important if additional
+ // on the passed timestamp and finds a prerequisite of the specified type
+ // (e.g., a source file). If there are multiple prerequisites of this type,
+ // then the first is returned (this can become important if additional
// prerequisites of the same type may get injected).
//
template <typename T>
- pair<const T*, target_state>
+ pair<optional<target_state>, const T&>
execute_prerequisites (action, const target&,
const timestamp&,
const prerequisite_filter& = nullptr);
- pair<const target*, target_state>
+ pair<optional<target_state>, const target&>
execute_prerequisites (const target_type&,
action, const target&,
const timestamp&,
const prerequisite_filter& = nullptr);
template <typename T>
- pair<const T*, target_state>
+ pair<optional<target_state>, const T&>
execute_prerequisites (const target_type&,
action, const target&,
const timestamp&,
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 049df0c..a6208d4 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -1059,7 +1059,7 @@ namespace build2
return r;
}
- pair<const target*, target_state>
+ pair<optional<target_state>, const target*>
execute_prerequisites (const target_type* tt,
action a, const target& t,
const timestamp& mt, const prerequisite_filter& pf)
@@ -1141,7 +1141,10 @@ namespace build2
}
assert (rt != nullptr);
- return make_pair (e ? rt : nullptr, rs);
+
+ return pair<optional<target_state>, const target*> (
+ e ? optional<target_state> () : rs,
+ tt != nullptr ? rt : nullptr);
}
target_state
diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx
index d39bf9c..46dece4 100644
--- a/build2/algorithm.ixx
+++ b/build2/algorithm.ixx
@@ -361,44 +361,46 @@ namespace build2
// If the first argument is NULL, then the result is treated as a boolean
// value.
//
- pair<const target*, target_state>
+ pair<optional<target_state>, const target*>
execute_prerequisites (const target_type*,
action, const target&,
const timestamp&, const prerequisite_filter&);
- inline pair<bool, target_state>
+ inline optional<target_state>
execute_prerequisites (action a, const target& t,
const timestamp& mt, const prerequisite_filter& pf)
{
- auto p (execute_prerequisites (nullptr, a, t, mt, pf));
- return make_pair (p.first != nullptr, p.second);
+ return execute_prerequisites (nullptr, a, t, mt, pf).first;
}
template <typename T>
- inline pair<const T*, target_state>
+ inline pair<optional<target_state>, const T&>
execute_prerequisites (action a, const target& t,
const timestamp& mt, const prerequisite_filter& pf)
{
auto p (execute_prerequisites (T::static_type, a, t, mt, pf));
- return make_pair (static_cast<const T*> (p.first), p.second);
+ return pair<optional<target_state>, const T&> (
+ p.first, static_cast<const T&> (p.second));
}
- inline pair<const target*, target_state>
+ inline pair<optional<target_state>, const target&>
execute_prerequisites (const target_type& tt,
action a, const target& t,
const timestamp& mt, const prerequisite_filter& pf)
{
- return execute_prerequisites (&tt, a, t, mt, pf);
+ auto p (execute_prerequisites (&tt, a, t, mt, pf));
+ return pair<optional<target_state>, const target&> (p.first, *p.second);
}
template <typename T>
- inline pair<const T*, target_state>
+ inline pair<optional<target_state>, const T&>
execute_prerequisites (const target_type& tt,
action a, const target& t,
const timestamp& mt, const prerequisite_filter& pf)
{
auto p (execute_prerequisites (tt, a, t, mt, pf));
- return make_pair (static_cast<const T*> (p.first), p.second);
+ return pair<optional<target_state>, const T&> (
+ p.first, static_cast<const T&> (p.second));
}
inline target_state
diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx
index c5ef480..7e37c44 100644
--- a/build2/cc/compile.cxx
+++ b/build2/cc/compile.cxx
@@ -993,7 +993,7 @@ namespace build2
// from the depdb cache or from the compiler run. Return whether the
// extraction process should be restarted.
//
- auto add = [&trace, &update, &pm, act, &t, lo, &dd, &bs, &t, this]
+ auto add = [&trace, &update, &pm, act, &t, lo, &dd, &bs, this]
(path f, bool cache) -> bool
{
// Find or maybe insert the target.
@@ -1486,22 +1486,21 @@ namespace build2
// out-of-date. Note that currently we treat all the prerequisites
// as potentially affecting the result (for simplicity/performance).
//
- const file* s;
- {
- timestamp mt;
+ timestamp mt;
- // If the depdb was overwritten or it's newer than the target, then
- // do unconditional update.
- //
- if (md.dd_mtime == timestamp_nonexistent ||
- md.dd_mtime > (mt = t.load_mtime ()))
- mt = timestamp_nonexistent;
+ // If the depdb was overwritten or it's newer than the target, then
+ // do unconditional update.
+ //
+ if (md.dd_mtime == timestamp_nonexistent ||
+ md.dd_mtime > (mt = t.load_mtime ()))
+ mt = timestamp_nonexistent;
- auto p (execute_prerequisites<file> (x_src, act, t, mt));
+ auto pr (execute_prerequisites<file> (x_src, act, t, mt));
- if ((s = p.first) == nullptr)
- return p.second;
- }
+ if (pr.first)
+ return *pr.first;
+
+ const file& s (pr.second);
const scope& bs (t.base_scope ());
const scope& rs (*bs.root_scope ());
@@ -1516,7 +1515,7 @@ namespace build2
// results in easier to read diagnostics.
//
path relo (relative (tp));
- path rels (relative (s->path ()));
+ path rels (relative (s.path ()));
append_options (args, t, c_poptions);
append_options (args, t, x_poptions);
@@ -1649,7 +1648,7 @@ namespace build2
if (verb >= 2)
print_process (args);
else if (verb)
- text << x_name << ' ' << *s;
+ text << x_name << ' ' << s;
try
{
diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx
index 884635a..2eb98c7 100644
--- a/build2/cli/rule.cxx
+++ b/build2/cli/rule.cxx
@@ -218,24 +218,24 @@ namespace build2
// as potentially affecting the result (think prologues/epilogues,
// etc).
//
- const cli* s;
- {
- // The rule has been matched which means the members should be
- // resolved and paths assigned.
- //
- auto p (
- execute_prerequisites<cli> (
- a, t, t.load_mtime (t.h->path ())));
- if ((s = p.first) == nullptr)
- return p.second;
- }
+ // The rule has been matched which means the members should be resolved
+ // and paths assigned.
+ //
+ auto pr (
+ execute_prerequisites<cli> (
+ a, t, t.load_mtime (t.h->path ())));
+
+ if (pr.first)
+ return *pr.first;
+
+ const cli& s (pr.second);
// Translate paths to relative (to working directory). This
// results in easier to read diagnostics.
//
path relo (relative (t.dir));
- path rels (relative (s->path ()));
+ path rels (relative (s.path ()));
const scope& rs (t.root_scope ());
@@ -246,7 +246,7 @@ namespace build2
// See if we need to pass --output-{prefix,suffix}
//
string prefix, suffix;
- match_stem (t.name, s->name, &prefix, &suffix);
+ match_stem (t.name, s.name, &prefix, &suffix);
if (!prefix.empty ())
{
@@ -281,7 +281,7 @@ namespace build2
if (verb >= 2)
print_process (args);
else if (verb)
- text << "cli " << *s;
+ text << "cli " << s;
try
{
diff --git a/build2/target b/build2/target
index 1a1fd8d..4316ff6 100644
--- a/build2/target
+++ b/build2/target
@@ -824,6 +824,7 @@ namespace build2
//
struct prerequisite_member
{
+ using scope_type = build2::scope;
using target_type = build2::target;
using prerequisite_type = build2::prerequisite;
using target_type_type = build2::target_type;
@@ -866,6 +867,12 @@ namespace build2
return target != nullptr ? target->name : prerequisite.name;
}
+ const dir_path&
+ dir () const
+ {
+ return target != nullptr ? target->dir : prerequisite.dir;
+ }
+
const optional<string>&
proj () const
{
@@ -876,6 +883,12 @@ namespace build2
: prerequisite.proj;
}
+ const scope_type&
+ scope () const
+ {
+ return target != nullptr ? target->base_scope () : prerequisite.scope;
+ }
+
const target_type&
search (const target_type& t) const
{
diff --git a/build2/utility b/build2/utility
index 2bcc52c..95766a4 100644
--- a/build2/utility
+++ b/build2/utility
@@ -16,8 +16,8 @@
#include <butl/ft/lang>
-#include <butl/utility> // combine_hash(), reverse_iterate(), casecmp(),
- // lcase(), etc
+#include <butl/utility> // combine_hash(), reverse_iterate(), case*(), etc
+#include <butl/standard-version>
#include <unordered_set>
@@ -104,6 +104,10 @@ namespace build2
//
extern process_path argv0;
+ // Build system driver version.
+ //
+ extern butl::standard_version build_version;
+
// Work/home directories (must be initialized in main()) and relative path
// calculation.
//
diff --git a/build2/utility.cxx b/build2/utility.cxx
index 475b72f..46b3811 100644
--- a/build2/utility.cxx
+++ b/build2/utility.cxx
@@ -14,6 +14,7 @@
#include <build2/diagnostics>
using namespace std;
+using namespace butl;
//
// <build2/types>
@@ -108,6 +109,7 @@ namespace build2
options ops;
process_path argv0;
+ standard_version build_version (BUILD2_VERSION_STR);
dir_path work;
dir_path home;
const dir_path* relative_base = &work;
diff --git a/build2/version-impl b/build2/version-impl
index 3df315e..b6c2a28 100644
--- a/build2/version-impl
+++ b/build2/version-impl
@@ -23,7 +23,7 @@
// 3.0.0-b2 02999952
//
#define BUILD2_VERSION 49901
-#define BUILD2_VERSION_STR "0.5.0-a1"
+#define BUILD2_VERSION_STR "0.5.0-a.1"
// Generally, we expect minor versions to be source code backwards-
// compatible, thought we might have a minimum version requirement.
diff --git a/build2/version/init.cxx b/build2/version/init.cxx
index 947f4a8..d111af5 100644
--- a/build2/version/init.cxx
+++ b/build2/version/init.cxx
@@ -28,6 +28,7 @@ namespace build2
static const path manifest ("manifest");
static const version_doc version_doc_;
+ static const version_in version_in_;
void
boot (scope& rs, const location& l, unique_ptr<module_base>& mod)
@@ -38,6 +39,7 @@ namespace build2
// Extract the version from the manifest file.
//
standard_version v;
+ dependency_constraints ds;
{
path f (rs.src_path () / manifest);
@@ -65,7 +67,73 @@ namespace build2
{
fail << "invalid standard version '" << nv.value << "': " << e;
}
- break;
+ }
+ else if (nv.name == "depends")
+ {
+ // According to the package manifest spec, the format of the
+ // 'depends' value is as follows:
+ //
+ // depends: [?][*] <alternatives> [; <comment>]
+ //
+ // <alternatives> := <dependency> [ '|' <dependency>]*
+ // <dependency> := <name> [<constraint>]
+ // <constraint> := <comparison> | <range>
+ // <comparison> := ('==' | '>' | '<' | '>=' | '<=') <version>
+ // <range> := ('(' | '[') <version> <version> (')' | ']')
+ //
+ // Note that we don't do exhaustive validation here leaving it
+ // to the package manager.
+ //
+ string v (move (nv.value));
+
+ size_t p;
+
+ // Get rid of the comment.
+ //
+ if ((p = v.find (';')) != string::npos)
+ v.resize (p);
+
+ // Get rid of conditional/runtime markers. Note that enither of
+ // them is valid in the rest of the value.
+ //
+ if ((p = v.find_last_of ("?*")) != string::npos)
+ v.erase (0, p + 1);
+
+ // Parse as |-separated "words".
+ //
+ for (size_t b (0), e (0); next_word (v, b, e, '|'); )
+ {
+ string d (v, b, e - b);
+ trim (d);
+
+ p = d.find_first_of (" \t=<>[(");
+ string n (d, 0, p);
+ string c (p != string::npos ? string (d, p) : string ());
+
+ trim (n);
+ trim (c);
+
+ // If this is a dependency on the build system itself, check
+ // it (so there is no need for explicit using build@X.Y.Z).
+ //
+ if (n == "build2" && !c.empty ())
+ try
+ {
+ standard_version_constraint vc (c);
+
+ if (!vc.satisfies (build_version))
+ fail (l) << "incompatible build2 version" <<
+ info << "running " << build_version.string () <<
+ info << "required " << vc.string ();
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version constraint for dependency "
+ << b << ": " << e;
+ }
+
+ ds.emplace (move (n), move (c));
+ }
}
}
}
@@ -91,7 +159,6 @@ namespace build2
// snapshot sn and id (e.g., commit date and id from git). If there is
// uncommitted stuff, then leave it as .z.
//
- bool patched (false);
if (v.snapshot () && v.snapshot_sn == standard_version::latest_sn)
{
snapshot ss (extract_snapshot (rs));
@@ -100,7 +167,6 @@ namespace build2
{
v.snapshot_sn = ss.sn;
v.snapshot_id = move (ss.id);
- patched = true;
}
}
@@ -155,7 +221,7 @@ namespace build2
// Create the module.
//
- mod.reset (new module (move (v), patched));
+ mod.reset (new module (move (v), move (ds)));
}
static void
@@ -211,6 +277,10 @@ namespace build2
r.insert<doc> (perform_update_id, "version.doc", version_doc_);
r.insert<doc> (perform_clean_id, "version.doc", version_doc_);
r.insert<doc> (configure_update_id, "version.doc", version_doc_);
+
+ r.insert<file> (perform_update_id, "version.in", version_in_);
+ r.insert<file> (perform_clean_id, "version.in", version_in_);
+ r.insert<file> (configure_update_id, "version.in", version_in_);
}
return true;
diff --git a/build2/version/module b/build2/version/module
index d5c1f01..6f913b3 100644
--- a/build2/version/module
+++ b/build2/version/module
@@ -5,6 +5,8 @@
#ifndef BUILD2_VERSION_MODULE
#define BUILD2_VERSION_MODULE
+#include <map>
+
#include <build2/types>
#include <build2/utility>
@@ -16,15 +18,19 @@ namespace build2
{
namespace version
{
+ // The 'depends' values from manifest.
+ //
+ using dependency_constraints = std::map<string, string>;
+
struct module: module_base
{
static const string name;
butl::standard_version version;
- bool version_patched; // True if snapshot was patched in.
+ dependency_constraints dependencies;
- module (butl::standard_version v, bool vp)
- : version (move (v)), version_patched (vp) {}
+ module (butl::standard_version v, dependency_constraints d)
+ : version (move (v)), dependencies (move (d)) {}
};
}
}
diff --git a/build2/version/rule b/build2/version/rule
index 75a8c12..186df59 100644
--- a/build2/version/rule
+++ b/build2/version/rule
@@ -30,6 +30,23 @@ namespace build2
static target_state
perform_update (action, const target&);
};
+
+ // Preprocess an .in file.
+ //
+ class version_in: public rule
+ {
+ public:
+ version_in () {}
+
+ virtual match_result
+ match (action, target&, const string&) const override;
+
+ virtual recipe
+ apply (action, target&) const override;
+
+ static target_state
+ perform_update (action, const target&);
+ };
}
}
diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx
index b6f09de..6909b92 100644
--- a/build2/version/rule.cxx
+++ b/build2/version/rule.cxx
@@ -4,9 +4,11 @@
#include <build2/version/rule>
+#include <build2/depdb>
#include <build2/scope>
#include <build2/target>
#include <build2/context>
+#include <build2/function>
#include <build2/algorithm>
#include <build2/filesystem>
#include <build2/diagnostics>
@@ -20,10 +22,35 @@ namespace build2
{
namespace version
{
+ // Return true if this prerequisite looks like 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<file> () || 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 ();
+ }
+
+ // version_doc
+ //
match_result version_doc::
match (action a, target& xt, const string&) const
{
- tracer trace ("version::version_file::match");
+ tracer trace ("version::version_doc::match");
doc& t (static_cast<doc&> (xt));
@@ -41,20 +68,8 @@ namespace build2
for (prerequisite_member p: group_prerequisite_members (a, t))
{
- if (!p.is_a<file> ())
- continue;
-
- const target& pt (p.search (t));
-
- if (pt.name != "manifest")
- continue;
-
- const scope* prs (pt.base_scope ().root_scope ());
-
- if (prs == nullptr || prs != &rs || pt.dir != rs.src_path ())
- continue;
-
- return true;
+ if (manifest_prerequisite (rs, p))
+ return true;
}
l4 ([&]{trace << "no manifest prerequisite for target " << t;});
@@ -66,7 +81,7 @@ namespace build2
{
doc& t (static_cast<doc&> (xt));
- // Derive file names for the members.
+ // Derive the file name.
//
t.derive_path ();
@@ -90,7 +105,7 @@ namespace build2
perform_update (action a, const target& xt)
{
const doc& t (xt.as<const doc&> ());
- const path& f (t.path ());
+ const path& tp (t.path ());
const scope& rs (t.root_scope ());
const module& m (*rs.modules.lookup<module> (module::name));
@@ -102,46 +117,447 @@ namespace build2
// module (we checked above that manifest and version files are in the
// same project).
//
- // That is, unless we patched the snapshot information in, in which case
- // we have to compare the contents.
+ // We also have to compare the contents (essentially using the file as
+ // its own depdb) in case of a snapshot since we can go back and forth
+ // between committed and uncommitted state that doesn't depend on any of
+ // our prerequisites.
//
{
- auto p (execute_prerequisites (a, t, t.load_mtime ()));
+ optional<target_state> ts (
+ execute_prerequisites (a, t, t.load_mtime ()));
- if (!p.first)
+ if (ts)
{
- if (!m.version_patched || !exists (f))
- return p.second;
+ if (!m.version.snapshot ()) // Everything came from the manifest.
+ return *ts;
try
{
- ifdstream ifs (f, fdopen_mode::in, ifdstream::badbit);
+ ifdstream ifs (tp, fdopen_mode::in, ifdstream::badbit);
string s;
getline (ifs, s);
if (s == m.version.string_project ())
- return p.second;
+ return *ts;
}
catch (const io_error& e)
{
- fail << "unable to read " << f << ": " << e;
+ fail << "unable to read " << tp << ": " << e;
}
}
}
if (verb >= 2)
- text << "cat >" << f;
+ text << "cat >" << tp;
try
{
- ofdstream ofs (f);
+ ofdstream ofs (tp);
+ auto_rmfile arm (tp);
ofs << m.version.string_project () << endl;
ofs.close ();
+ arm.cancel ();
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write " << tp << ": " << e;
+ }
+
+ t.mtime (system_clock::now ());
+ return target_state::changed;
+ }
+
+ // version_in
+ //
+ match_result version_in::
+ match (action a, target& xt, const string&) const
+ {
+ tracer trace ("version::version_in::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))
+ {
+ fm = fm || manifest_prerequisite (rs, p);
+ fi = fi || p.is_a<in> ();
+ }
+
+ if (!fm)
+ l4 ([&]{trace << "no manifest prerequisite for target " << t;});
+
+ if (!fi)
+ l4 ([&]{trace << "no in file prerequisite for target " << t;});
+
+ return fm && fi;
+ }
+
+ recipe version_in::
+ 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);
+
+ // Match prerequisite members.
+ //
+ match_prerequisite_members (a, t);
+
+ 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.
+ }
+ }
+
+ target_state version_in::
+ perform_update (action a, const target& xt)
+ {
+ tracer trace ("version::version_in::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));
+
+ // Determine if anything needs to be updated.
+ //
+ 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);
+
+ const in& i (pr.second);
+ const path& ip (i.path ());
+
+ // We use depdb to track both the .in file and the potentially patched
+ // snapshot.
+ //
+ {
+ depdb dd (tp + ".d");
+
+ // First should come the rule name/version.
+ //
+ if (dd.expect ("version.in 1") != nullptr)
+ l4 ([&]{trace << "rule mismatch forcing update of " << t;});
+
+ // Then the .in file.
+ //
+ 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;
+
+ dd.close ();
+ }
+
+ // 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 root scope).
+ //
+ auto subst_self = [&rs, &m] (const location& l, const string& s)
+ {
+ if (lookup x = rs.vars[s])
+ {
+ // Call the string() function to convert the value.
+ //
+ value v (*x);
+
+ return convert<string> (
+ functions.call (
+ "string", vector_view<value> (&v, 1), l));
+ }
+ else
+ fail (l) << "undefined project variable '" << s << "'" << endf;
+ };
+
+ // 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).
+ //
+ 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));
+
+ if (i == m.dependencies.end ())
+ fail (l) << "unknown dependency '" << n << "'";
+
+ if (i->second.empty ())
+ fail (l) << "no version constraint for dependency " << n;
+
+ c = standard_version_constraint (i->second);
+ }
+ catch (const invalid_argument& e)
+ {
+ fail (l) << "invalid version constraint for dependency " << n
+ << ": " << e;
+ }
+
+ // 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 (j == string::npos || (s[j] == ',' && s.back () != ')'))
+ fail (l) << "missing closing ')'";
+
+ string vm (s, i, j - i); // VER macro.
+ string sm (s[j] == ',' // SNAP macro.
+ ? string (s, j + 1, s.size () - j - 2)
+ : string ());
+
+ trim (vm);
+ trim (sm);
+
+ auto cond = [&l, &c, &vm, &sm] () -> string
+ {
+ auto& miv (c.min_version);
+ auto& mav (c.max_version);
+
+ bool mio (c.min_open);
+ bool mao (c.max_open);
+
+ if (sm.empty () &&
+ ((miv && miv->snapshot ()) ||
+ (mav && mav->snapshot ())))
+ fail (l) << "snapshot macro required for " << c.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 (s[1] == 'o') // condition
+ return cond ();
+
+ string r;
+
+ r += "#if !(" + cond () + ")\n";
+ r += "# error incompatible " + n + " version, ";
+ r += n + ' ' + c.string () + " is required\n";
+ r += "#endif";
+
+ return r;
+ }
+ else
+ fail (l) << "unknown dependency substitution '" << s << "'" << endf;
+ };
+
+ if (verb >= 2)
+ text << "ver -o " << tp << ' ' << ip;
+ else if (verb)
+ text << "ver " << tp;
+
+ // 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);
+
+ 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.
+
+ // Scan the line looking for substiutions in the $<pkg>.<rest>$
+ // form. Treat $$ as an escape sequence.
+ //
+ for (size_t b (0), n, d; b != (n = s.size ()); b += d)
+ {
+ d = 1;
+
+ if (s[b] != '$')
+ continue;
+
+ // Find the other end.
+ //
+ size_t e (b + 1);
+ for (; e != (n = s.size ()); ++e)
+ {
+ if (s[e] == '$')
+ {
+ if (e + 1 != n && s[e + 1] == '$') // Escape.
+ s.erase (e, 1); // Keep one, erase the other.
+ else
+ break;
+ }
+ }
+
+ if (e == n)
+ fail (l) << "unterminated '$'";
+
+ if (e - b == 1) // Escape.
+ {
+ s.erase (b, 1); // Keep one, erase the other.
+ continue;
+ }
+
+ // We have a substition with b pointing to the opening $ and e --
+ // to the closing. Split it into the package name and the trailer.
+ //
+ size_t p (s.find ('.', b + 1));
+
+ if (p == string::npos || p > e)
+ fail (l) << "invalid substitution: missing package name";
+
+ string sn (s, b + 1, p - b - 1);
+ string st (s, p + 1, e - p - 1);
+ string sr (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 ();
+ }
+
+ what = "write"; whom = &tp;
+ ofs << s << endl;
+ }
+
+ what = "close"; whom = &tp;
+ ofs.close ();
+ arm.cancel ();
+
+ what = "close"; whom = &ip;
+ ifs.close ();
}
catch (const io_error& e)
{
- fail << "unable to write " << f << ": " << e;
+ fail << "unable to " << what << ' ' << whom << ": " << e;
}
t.mtime (system_clock::now ());