From da9cbf29c403d27c2940f9b31199c4648f8ae4a1 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 1 Aug 2019 16:10:48 +0300 Subject: Move version build system module to separate library --- bootstrap-mingw.bat | 2 +- bootstrap-msvc.bat | 2 +- bootstrap.gmake | 4 +- bootstrap.sh | 2 +- build2/b.cxx | 5 +- build2/bash/init.cxx | 2 +- build2/buildfile | 4 +- build2/version/init.cxx | 392 ----------------------------------- build2/version/init.hxx | 31 --- build2/version/module.cxx | 15 -- build2/version/module.hxx | 64 ------ build2/version/rule.cxx | 334 ------------------------------ build2/version/rule.hxx | 52 ----- build2/version/snapshot-git.cxx | 175 ---------------- build2/version/snapshot.cxx | 39 ---- build2/version/snapshot.hxx | 34 ---- build2/version/utility.cxx | 81 -------- build2/version/utility.hxx | 25 --- libbuild2/buildfile | 2 +- libbuild2/version/buildfile | 66 ++++++ libbuild2/version/export.hxx | 34 ++++ libbuild2/version/init.cxx | 407 +++++++++++++++++++++++++++++++++++++ libbuild2/version/init.hxx | 28 +++ libbuild2/version/module.cxx | 15 ++ libbuild2/version/module.hxx | 64 ++++++ libbuild2/version/rule.cxx | 334 ++++++++++++++++++++++++++++++ libbuild2/version/rule.hxx | 52 +++++ libbuild2/version/snapshot-git.cxx | 175 ++++++++++++++++ libbuild2/version/snapshot.cxx | 39 ++++ libbuild2/version/snapshot.hxx | 34 ++++ libbuild2/version/utility.cxx | 81 ++++++++ libbuild2/version/utility.hxx | 25 +++ tests/libbuild2/buildfile | 1 + tests/libbuild2/driver.cxx | 2 + 34 files changed, 1368 insertions(+), 1254 deletions(-) delete mode 100644 build2/version/init.cxx delete mode 100644 build2/version/init.hxx delete mode 100644 build2/version/module.cxx delete mode 100644 build2/version/module.hxx delete mode 100644 build2/version/rule.cxx delete mode 100644 build2/version/rule.hxx delete mode 100644 build2/version/snapshot-git.cxx delete mode 100644 build2/version/snapshot.cxx delete mode 100644 build2/version/snapshot.hxx delete mode 100644 build2/version/utility.cxx delete mode 100644 build2/version/utility.hxx create mode 100644 libbuild2/version/buildfile create mode 100644 libbuild2/version/export.hxx create mode 100644 libbuild2/version/init.cxx create mode 100644 libbuild2/version/init.hxx create mode 100644 libbuild2/version/module.cxx create mode 100644 libbuild2/version/module.hxx create mode 100644 libbuild2/version/rule.cxx create mode 100644 libbuild2/version/rule.hxx create mode 100644 libbuild2/version/snapshot-git.cxx create mode 100644 libbuild2/version/snapshot.cxx create mode 100644 libbuild2/version/snapshot.hxx create mode 100644 libbuild2/version/utility.cxx create mode 100644 libbuild2/version/utility.hxx diff --git a/bootstrap-mingw.bat b/bootstrap-mingw.bat index 5061753..e4caf8e 100644 --- a/bootstrap-mingw.bat +++ b/bootstrap-mingw.bat @@ -65,7 +65,6 @@ set "src=%src% build2\bin" set "src=%src% build2\c" set "src=%src% build2\cc" set "src=%src% build2\cxx" -set "src=%src% build2\version" set "src=%src% libbuild2" set "src=%src% libbuild2\config" @@ -73,6 +72,7 @@ set "src=%src% libbuild2\dist" set "src=%src% libbuild2\test" set "src=%src% libbuild2\test\script" set "src=%src% libbuild2\install" +set "src=%src% libbuild2\version" set "src=%src% libbuild2\in" set "src=%src% %libbutl%\libbutl" diff --git a/bootstrap-msvc.bat b/bootstrap-msvc.bat index e4e2a66..1facca5 100644 --- a/bootstrap-msvc.bat +++ b/bootstrap-msvc.bat @@ -96,7 +96,6 @@ set "src=%src% build2\bin" set "src=%src% build2\c" set "src=%src% build2\cc" set "src=%src% build2\cxx" -set "src=%src% build2\version" set "src=%src% libbuild2" set "src=%src% libbuild2\config" @@ -104,6 +103,7 @@ set "src=%src% libbuild2\dist" set "src=%src% libbuild2\test" set "src=%src% libbuild2\test\script" set "src=%src% libbuild2\install" +set "src=%src% libbuild2\version" set "src=%src% libbuild2\in" set "src=%src% %libbutl%\libbutl" diff --git a/bootstrap.gmake b/bootstrap.gmake index 793f3d0..e4d4b33 100644 --- a/bootstrap.gmake +++ b/bootstrap.gmake @@ -133,8 +133,7 @@ build2_sub := \ bin \ c \ cc \ -cxx \ -version +cxx libbuild2_sub := \ config \ @@ -142,6 +141,7 @@ dist \ test/script \ test \ install \ +version \ in build2_src := $(wildcard $(src_root)/build2/*.cxx) diff --git a/bootstrap.sh b/bootstrap.sh index 7e754b0..8de5e3c 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -122,7 +122,6 @@ src="$src build2/c/*.cxx" src="$src build2/cc/*.cxx" src="$src build2/cxx/*.cxx" src="$src build2/cli/*.cxx" -src="$src build2/version/*.cxx" src="$src build2/bash/*.cxx" src="$src libbuild2/*.cxx" @@ -131,6 +130,7 @@ src="$src libbuild2/dist/*.cxx" src="$src libbuild2/test/*.cxx" src="$src libbuild2/test/script/*.cxx" src="$src libbuild2/install/*.cxx" +src="$src libbuild2/version/*.cxx" src="$src libbuild2/in/*.cxx" src="$src $libbutl/libbutl/*.cxx" diff --git a/build2/b.cxx b/build2/b.cxx index 907784b..a016278 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -52,7 +52,7 @@ #include #include -#include +#include #include #include @@ -449,8 +449,7 @@ main (int argc, char* argv[]) reg (&test::build2_test_load); reg (&install::build2_install_load); - bm["version"] = mf {"version", &version::boot, &version::init}; - + reg (&version::build2_version_load); reg (&in::build2_in_load); bm["bin.vars"] = mf {"bin.vars", nullptr, &bin::vars_init}; diff --git a/build2/bash/init.cxx b/build2/bash/init.cxx index a23bc61..146e680 100644 --- a/build2/bash/init.cxx +++ b/build2/bash/init.cxx @@ -36,7 +36,7 @@ namespace build2 tracer trace ("bash::init"); l5 ([&]{trace << "for " << bs;}); - // Load in.base (in.* varibales, in{} target type). + // Load in.base (in.* variables, in{} target type). // if (!cast_false (rs["in.base.loaded"])) load_module (rs, rs, "in.base", l); diff --git a/build2/buildfile b/build2/buildfile index a940793..ea39e87 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -8,9 +8,9 @@ import libs += libpkgconf%lib{pkgconf} include ../libbuild2/ libs += ../libbuild2/lib{build2} -for m: in +for m: version in { - include ../libbuild2/in/ + include ../libbuild2/$m/ libs += ../libbuild2/$m/lib{build2-$m} } diff --git a/build2/version/init.cxx b/build2/version/init.cxx deleted file mode 100644 index 30f0f45..0000000 --- a/build2/version/init.cxx +++ /dev/null @@ -1,392 +0,0 @@ -// file : build2/version/init.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -using namespace std; -using namespace butl; - -namespace build2 -{ - namespace version - { - static const path manifest_file ("manifest"); - - static const in_rule in_rule_; - static const manifest_install_rule manifest_install_rule_; - - bool - boot (scope& rs, const location& l, unique_ptr& mod) - { - tracer trace ("version::boot"); - l5 ([&]{trace << "for " << rs;}); - - // Extract the version from the manifest file. As well as summary and - // url while at it. - // - // Also, as a sanity check, verify the package name matches the build - // system project name. - // - string sum; - string url; - - standard_version v; - dependencies ds; - { - path f (rs.src_path () / manifest_file); - - try - { - if (!file_exists (f)) - fail (l) << "no manifest file in " << rs.src_path (); - - ifdstream ifs (f); - manifest_parser p (ifs, f.string ()); - - manifest_name_value nv (p.next ()); - if (!nv.name.empty () || nv.value != "1") - fail (l) << "unsupported manifest format in " << f; - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - if (nv.name == "name") - { - auto& pn (cast (rs.vars[var_project])); - - if (nv.value != pn.string ()) - { - path bf (rs.src_path () / rs.root_extra->bootstrap_file); - location ml (&f, nv.value_line, nv.value_column); - location bl (&bf); - - fail (ml) << "package name " << nv.value << " does not match " - << "build system project name " << pn << - info (bl) << "build system project name specified here"; - } - } - if (nv.name == "summary") - sum = move (nv.value); - else if (nv.name == "url") - url = move (nv.value); - else if (nv.name == "version") - { - try - { - // Allow the package stub versions in the 0+ form. - // While not standard, we want to use the version module for - // packaging stubs. - // - v = standard_version (nv.value, standard_version::allow_stub); - } - catch (const invalid_argument& e) - { - fail << "invalid standard version '" << nv.value << "': " << e; - } - } - else if (nv.name == "depends") - { - // According to the package manifest spec, the format of the - // 'depends' value is as follows: - // - // depends: [?][*] [; ] - // - // := [ '|' ]* - // := [] - // := | - // := ('==' | '>' | '<' | '>=' | '<=') - // := ('(' | '[') (')' | ']') - // - // 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); - - try - { - package_name pn (move (n)); - string v (pn.variable ()); - - ds.emplace (move (v), dependency {move (pn), move (c)}); - } - catch (const invalid_argument& e) - { - fail (l) << "invalid package name for dependency " - << d << ": " << e; - } - } - } - } - } - catch (const manifest_parsing& e) - { - location l (&f, e.line, e.column); - fail (l) << e.description; - } - catch (const io_error& e) - { - fail (l) << "unable to read from " << f << ": " << e; - } - catch (const system_error& e) // EACCES, etc. - { - fail (l) << "unable to access manifest " << f << ": " << e; - } - - if (v.empty ()) - fail (l) << "no version in " << f; - } - - // If this is the latest snapshot (i.e., the -a.1.z kind), then load the - // snapshot number and id (e.g., commit date and id from git). - // - bool committed (true); - bool rewritten (false); - if (v.snapshot () && v.snapshot_sn == standard_version::latest_sn) - { - snapshot ss (extract_snapshot (rs)); - - if (!ss.empty ()) - { - v.snapshot_sn = ss.sn; - v.snapshot_id = move (ss.id); - committed = ss.committed; - rewritten = true; - } - else - committed = false; - } - - // If there is a dependency on the build system itself, check it (so - // there is no need for explicit using build@X.Y.Z). - // - { - auto i (ds.find ("build2")); - - if (i != ds.end () && !i->second.constraint.empty ()) - try - { - check_build_version ( - standard_version_constraint (i->second.constraint, v), l); - } - catch (const invalid_argument& e) - { - fail (l) << "invalid version constraint for dependency build2 " - << i->second.constraint << ": " << e; - } - } - - // Set all the version.* variables. - // - auto& vp (var_pool.rw (rs)); - - auto set = [&vp, &rs] (const char* var, auto val) - { - using T = decltype (val); - auto& v (vp.insert (var, variable_visibility::project)); - rs.assign (v) = move (val); - }; - - if (!sum.empty ()) rs.assign (var_project_summary) = move (sum); - if (!url.empty ()) rs.assign (var_project_url) = move (url); - - set ("version", v.string ()); // Project version (var_version). - - set ("version.project", v.string_project ()); - set ("version.project_number", v.version); - - // Enough of project version for unique identification (can be used in - // places like soname, etc). - // - set ("version.project_id", v.string_project_id ()); - - set ("version.stub", v.stub ()); // bool - - set ("version.epoch", uint64_t (v.epoch)); - - set ("version.major", uint64_t (v.major ())); - set ("version.minor", uint64_t (v.minor ())); - set ("version.patch", uint64_t (v.patch ())); - - optional a (v.alpha ()); - optional b (v.beta ()); - - set ("version.alpha", a.has_value ()); - set ("version.beta", b.has_value ()); - set ("version.pre_release", v.pre_release ().has_value ()); - set ("version.pre_release_string", v.string_pre_release ()); - set ("version.pre_release_number", uint64_t (a ? *a : b ? *b : 0)); - - set ("version.snapshot", v.snapshot ()); // bool - set ("version.snapshot_sn", v.snapshot_sn); // uint64 - set ("version.snapshot_id", v.snapshot_id); // string - set ("version.snapshot_string", v.string_snapshot ()); - set ("version.snapshot_committed", committed); // bool - - set ("version.revision", uint64_t (v.revision)); - - // Create the module. - // - mod.reset (new module (cast (rs.vars[var_project]), - move (v), - committed, - rewritten, - move (ds))); - - return true; // Init first (dist.package, etc). - } - - static void - dist_callback (const path&, const scope&, void*); - - bool - init (scope& rs, - scope&, - const location& l, - unique_ptr& mod, - bool first, - bool, - const variable_map&) - { - tracer trace ("version::init"); - - if (!first) - fail (l) << "multiple version module initializations"; - - // Load in.base (in.* varibales, in{} target type). - // - if (!cast_false (rs["in.base.loaded"])) - load_module (rs, rs, "in.base", l); - - module& m (static_cast (*mod)); - const standard_version& v (m.version); - - // If the dist module is used, set its dist.package and register the - // post-processing callback. - // - if (auto* dm = rs.lookup_module (dist::module::name)) - { - // Make sure dist is init'ed, not just boot'ed. - // - if (!cast_false (rs["dist.loaded"])) - load_module (rs, rs, "dist", l); - - m.dist_uncommitted = cast_false (rs["config.dist.uncommitted"]); - - // Don't touch if dist.package was set by the user. - // - value& val (rs.assign (dm->var_dist_package)); - - if (!val) - { - string p (cast (rs.vars[var_project]).string ()); - p += '-'; - p += v.string (); - val = move (p); - - // Only register the post-processing callback if this is a rewritten - // snapshot. - // - if (m.rewritten) - dm->register_callback (dir_path (".") / manifest_file, - &dist_callback, - &m); - } - } - - // Register rules. - // - { - auto& r (rs.rules); - - r.insert (perform_update_id, "version.in", in_rule_); - r.insert (perform_clean_id, "version.in", in_rule_); - r.insert (configure_update_id, "version.in", in_rule_); - - if (cast_false (rs["install.booted"])) - { - r.insert ( - perform_install_id, "version.manifest", manifest_install_rule_); - } - } - - return true; - } - - static void - dist_callback (const path& f, const scope& rs, void* data) - { - module& m (*static_cast (data)); - - // Complain if this is an uncommitted snapshot. - // - if (!m.committed && !m.dist_uncommitted) - fail << "distribution of uncommitted project " << rs.src_path () << - info << "specify config.dist.uncommitted=true to force"; - - // The plan is simple: fixing up the version in a temporary file then - // move it to the original. - // - try - { - auto_rmfile t (fixup_manifest (f, - path::temp_path ("manifest"), - m.version)); - - mvfile (t.path, f, (cpflags::overwrite_content | - cpflags::overwrite_permissions)); - t.cancel (); - } - catch (const io_error& e) - { - fail << "unable to overwrite " << f << ": " << e; - } - catch (const system_error& e) // EACCES, etc. - { - fail << "unable to overwrite " << f << ": " << e; - } - } - } -} diff --git a/build2/version/init.hxx b/build2/version/init.hxx deleted file mode 100644 index ef3688a..0000000 --- a/build2/version/init.hxx +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/version/init.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_INIT_HXX -#define BUILD2_VERSION_INIT_HXX - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - bool - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_VERSION_INIT_HXX diff --git a/build2/version/module.cxx b/build2/version/module.cxx deleted file mode 100644 index 1811cfc..0000000 --- a/build2/version/module.cxx +++ /dev/null @@ -1,15 +0,0 @@ -// file : build2/version/module.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -using namespace std; - -namespace build2 -{ - namespace version - { - const string module::name ("version"); - } -} diff --git a/build2/version/module.hxx b/build2/version/module.hxx deleted file mode 100644 index d2b681c..0000000 --- a/build2/version/module.hxx +++ /dev/null @@ -1,64 +0,0 @@ -// file : build2/version/module.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_MODULE_HXX -#define BUILD2_VERSION_MODULE_HXX - -#include - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - // A map of package names sanitized for use in variable names to the - // 'depends' values from manifest. - // - using package_name = project_name; - - struct dependency - { - package_name name; - string constraint; - }; - - using dependencies = std::map; - - struct module: module_base - { - using dependencies_type = version::dependencies; - - static const string name; - - // The project variable value sanitized for use in variable names. - // - const string project; - - butl::standard_version version; - bool committed; // Whether this is a committed snapshot. - bool rewritten; // Whether this is a rewritten .z snapshot. - - dependencies_type dependencies; - - bool dist_uncommitted = false; - - module (const project_name& p, - butl::standard_version v, - bool c, - bool r, - dependencies_type d) - : project (p.variable ()), - version (move (v)), - committed (c), - rewritten (r), - dependencies (move (d)) {} - }; - } -} - -#endif // BUILD2_VERSION_MODULE_HXX diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx deleted file mode 100644 index f110e3e..0000000 --- a/build2/version/rule.cxx +++ /dev/null @@ -1,334 +0,0 @@ -// file : build2/version/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include -#include - -#include - -#include -#include - -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 () || 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 (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 (); - } - - // 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::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 ()); - - // 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 () || 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::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); - } - } -} diff --git a/build2/version/rule.hxx b/build2/version/rule.hxx deleted file mode 100644 index 7bfb783..0000000 --- a/build2/version/rule.hxx +++ /dev/null @@ -1,52 +0,0 @@ -// file : build2/version/rule.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_RULE_HXX -#define BUILD2_VERSION_RULE_HXX - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace version - { - // Preprocess an .in file that depends on manifest. - // - class in_rule: public in::rule - { - public: - in_rule (): rule ("version.in 2", "version.in") {} - - virtual bool - match (action, target&, const string&) const override; - - virtual string - lookup (const location&, - action, - const target&, - const string&) const override; - }; - - // Pre-process manifest before installation to patch in the version. - // - class manifest_install_rule: public install::file_rule - { - public: - manifest_install_rule () {} - - virtual bool - match (action, target&, const string&) const override; - - virtual auto_rmfile - install_pre (const file&, const install_dir&) const override; - }; - } -} - -#endif // BUILD2_VERSION_RULE_HXX diff --git a/build2/version/snapshot-git.cxx b/build2/version/snapshot-git.cxx deleted file mode 100644 index f7993d2..0000000 --- a/build2/version/snapshot-git.cxx +++ /dev/null @@ -1,175 +0,0 @@ -// file : build2/version/snapshot-git.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include // time_t - -#include - -#include - -using namespace std; -using namespace butl; - -namespace build2 -{ - namespace version - { - snapshot - extract_snapshot_git (const dir_path& src_root) - { - snapshot r; - const char* d (src_root.string ().c_str ()); - - // First check whether the working directory is clean. There doesn't - // seem to be a way to do everything in a single invocation (the - // porcelain v2 gives us the commit id but not timestamp). - // - - // If git status --porcelain returns anything, then the working - // directory is not clean. - // - { - const char* args[] {"git", "-C", d, "status", "--porcelain", nullptr}; - r.committed = run ( - 3 /* verbosity */, - args, - [](string& s, bool) {return move (s);}).empty (); - } - - // Now extract the commit id and date. One might think that would be - // easy... Commit id is a SHA1 hash of the commit object. And commit - // object looks like this: - // - // commit \0 - // - // - // Where is the size of and is the output of: - // - // git cat-file commit HEAD - // - // There is also one annoying special case: new repository without any - // commits. In this case the above command will fail (with diagnostics - // and non-zero exit code) because there is no HEAD. Of course, it can - // also fail for other reason (like broken repository) which would be - // hard to distinguish. Note, however, that we just ran git status and - // it would have most likely failed if this were the case. So here we - // (reluctantly) assume that the only reason git cat-file fails is if - // there is no HEAD (that we equal with the "new repository" condition - // which is, strictly speaking, might not be the case either). So we - // suppress any diagnostics, and handle non-zero exit code. - // - string data; - - const char* args[] { - "git", "-C", d, "cat-file", "commit", "HEAD", nullptr}; - process pr (run_start (3 /* verbosity */, - args, - 0 /* stdin */, - -1 /* stdout */, - false /* error */)); - - string l; - try - { - ifdstream is (move (pr.in_ofd), ifdstream::badbit); - - while (!eof (getline (is, l))) - { - data += l; - data += '\n'; // We assume there is always a newline. - - if (r.sn == 0 && l.compare (0, 10, "committer ") == 0) - try - { - // The line format is: - // - // committer - // - // For example: - // - // committer John Doe 1493117819 +0200 - // - // The timestamp is in seconds since UNIX epoch. The timezone - // appears to be always numeric (+0000 for UTC). Note that - // timestamp appears to be already in UTC with timezone being just - // for information it seems. - // - size_t p1 (l.rfind (' ')); // Can't be npos. - - size_t p2 (l.rfind (' ', p1 - 1)); - if (p2 == string::npos) - throw invalid_argument ("missing timestamp"); - - string ts (l, p2 + 1, p1 - p2 - 1); - time_t t (static_cast (stoull (ts))); - -#if 0 - string tz (l, p1 + 1); - - if (tz.size () != 5) - throw invalid_argument ("invalid timezone"); - - unsigned long h (stoul (string (tz, 1, 2))); - unsigned long m (stoul (string (tz, 3, 2))); - unsigned long s (h * 3600 + m * 60); - - // The timezone indicates where the timestamp was generated so to - // convert to UTC we need to invert the sign. - // - switch (tz[0]) - { - case '+': t -= s; break; - case '-': t += s; break; - default: throw invalid_argument ("invalid timezone sign"); - } -#endif - // Represent as YYYYMMDDhhmmss. - // - r.sn = stoull (to_string (system_clock::from_time_t (t), - "%Y%m%d%H%M%S", - false /* special */, - false /* local (already in UTC) */)); - } - catch (const invalid_argument& e) - { - fail << "unable to extract git commit date from '" << l << "': " - << e; - } - } - - is.close (); - } - catch (const io_error&) - { - // Presumably the child process failed. Let run_finish() deal with - // that. - } - - if (!run_finish (args, pr, false /* error */, l)) - { - // Presumably new repository without HEAD. Return uncommitted snapshot - // with UNIX epoch as timestamp. - // - r.sn = 19700101000000ULL; - r.committed = false; - return r; - } - - if (r.sn == 0) - fail << "unable to extract git commit id/date for " << src_root; - - if (r.committed) - { - sha1 cs; - cs.append ("commit " + to_string (data.size ())); // Includes '\0'. - cs.append (data.c_str (), data.size ()); - r.id.assign (cs.string (), 12); // 12-characters abbreviated commit id. - } - else - r.sn++; // Add a second. - - return r; - } - } -} diff --git a/build2/version/snapshot.cxx b/build2/version/snapshot.cxx deleted file mode 100644 index b43e083..0000000 --- a/build2/version/snapshot.cxx +++ /dev/null @@ -1,39 +0,0 @@ -// file : build2/version/snapshot.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include - -using namespace std; - -namespace build2 -{ - namespace version - { - snapshot - extract_snapshot_git (const dir_path&); - - static const path git (".git"); - - snapshot - extract_snapshot (const scope& rs) - { - // Ignore errors when checking for existence since we may be iterating - // over directories past any reasonable project boundaries. - // - for (dir_path d (rs.src_path ()); !d.empty (); d = d.directory ()) - { - // .git can be either a directory or a file in case of a submodule. - // - if (butl::entry_exists (d / git, - true /* follow_symlinks */, - true /* ignore_errors */)) - return extract_snapshot_git (d); - } - - return snapshot (); - } - } -} diff --git a/build2/version/snapshot.hxx b/build2/version/snapshot.hxx deleted file mode 100644 index 824ec89..0000000 --- a/build2/version/snapshot.hxx +++ /dev/null @@ -1,34 +0,0 @@ -// file : build2/version/snapshot.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_SNAPSHOT_HXX -#define BUILD2_VERSION_SNAPSHOT_HXX - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - struct snapshot - { - uint64_t sn = 0; - string id; - bool committed = false; - - bool - empty () const {return sn == 0;} - }; - - // Return empty snapshot if unknown scm or uncommitted. - // - snapshot - extract_snapshot (const scope& rs); - } -} - -#endif // BUILD2_VERSION_SNAPSHOT_HXX diff --git a/build2/version/utility.cxx b/build2/version/utility.cxx deleted file mode 100644 index 8286ff8..0000000 --- a/build2/version/utility.cxx +++ /dev/null @@ -1,81 +0,0 @@ -// file : build2/version/utility.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include - -#include - -using namespace butl; - -namespace build2 -{ - namespace version - { - auto_rmfile - fixup_manifest (const path& in, path out, const standard_version& v) - { - auto_rmfile r (move (out), !dry_run /* active */); - - if (!dry_run) - { - try - { - permissions perm (path_permissions (in)); - - ifdstream ifs (in); - manifest_parser p (ifs, in.string ()); - - auto_fd ofd (fdopen (r.path, - fdopen_mode::out | - fdopen_mode::create | - fdopen_mode::exclusive | - fdopen_mode::binary, - perm)); - - ofdstream ofs (move (ofd)); - manifest_serializer s (ofs, r.path.string ()); - - manifest_name_value nv (p.next ()); - assert (nv.name.empty () && nv.value == "1"); // We just loaded it. - s.next (nv.name, nv.value); - - for (nv = p.next (); !nv.empty (); nv = p.next ()) - { - if (nv.name == "version") - nv.value = v.string (); - - s.next (nv.name, nv.value); - } - - s.next (nv.name, nv.value); // End of manifest. - s.next (nv.name, nv.value); // End of stream. - - ofs.close (); - ifs.close (); - } - catch (const manifest_parsing& e) - { - location l (&in, e.line, e.column); - fail (l) << e.description; - } - catch (const manifest_serialization& e) - { - location l (&r.path); - fail (l) << e.description; - } - catch (const io_error& e) - { - fail << "io error: " << e << - info << "while reading " << in << - info << "while writing " << r.path; - } - } - - return r; - } - } -} diff --git a/build2/version/utility.hxx b/build2/version/utility.hxx deleted file mode 100644 index 83bb91c..0000000 --- a/build2/version/utility.hxx +++ /dev/null @@ -1,25 +0,0 @@ -// file : build2/version/utility.hxx -*- C++ -*- -// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_UTILITY_HXX -#define BUILD2_VERSION_UTILITY_HXX - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - // Re-serialize the manifest fixing up the version. Note that this will - // not preserve comments. Probably acceptable for snapshots. - // - auto_rmfile - fixup_manifest (const path& in, path out, const standard_version&); - } -} - -#endif // BUILD2_VERSION_UTILITY_HXX diff --git a/libbuild2/buildfile b/libbuild2/buildfile index b015d21..ab95098 100644 --- a/libbuild2/buildfile +++ b/libbuild2/buildfile @@ -2,7 +2,7 @@ # copyright : Copyright (c) 2014-2019 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -./: lib{build2} in/ +./: lib{build2} version/ in/ import int_libs = libbutl%lib{butl} diff --git a/libbuild2/version/buildfile b/libbuild2/version/buildfile new file mode 100644 index 0000000..e9d4905 --- /dev/null +++ b/libbuild2/version/buildfile @@ -0,0 +1,66 @@ +# file : libbuild2/version/buildfile +# copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +import int_libs = libbutl%lib{butl} + +include ../ +int_libs += ../lib{build2} + +include ../in/ +int_libs += ../in/lib{build2-in} + +./: lib{build2-version}: libul{build2-version}: \ + {hxx ixx txx cxx}{** -**.test...} \ + $int_libs + +# Unit tests. +# +exe{*.test}: +{ + test = true + install = false +} + +for t: cxx{**.test...} +{ + d = $directory($t) + n = $name($t)... + + ./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} + $d/exe{$n}: libul{build2-version}: bin.whole = false +} + +# Build options. +# +obja{*}: cxx.poptions += -DLIBBUILD2_VERSION_STATIC_BUILD +objs{*}: cxx.poptions += -DLIBBUILD2_VERSION_SHARED_BUILD + +# Export options. +# +lib{build2-version}: +{ + cxx.export.poptions = "-I$out_root" "-I$src_root" + cxx.export.libs = $int_libs +} + +liba{build2-version}: cxx.export.poptions += -DLIBBUILD2_VERSION_STATIC +libs{build2-version}: cxx.export.poptions += -DLIBBUILD2_VERSION_SHARED + +# For pre-releases use the complete version to make sure they cannot be used +# in place of another pre-release or the final version. See the version module +# for details on the version.* variable values. +# +if $version.pre_release + lib{build2-version}: bin.lib.version = @"-$version.project_id" +else + lib{build2-version}: bin.lib.version = @"-$version.major.$version.minor" + +# Install into the libbuild2/version/ subdirectory of, say, /usr/include/ +# recreating subdirectories. +# +{hxx ixx txx}{*}: +{ + install = include/libbuild2/version/ + install.subdirs = true +} diff --git a/libbuild2/version/export.hxx b/libbuild2/version/export.hxx new file mode 100644 index 0000000..c76cd8a --- /dev/null +++ b/libbuild2/version/export.hxx @@ -0,0 +1,34 @@ +#pragma once + +// Normally we don't export class templates (but do complete specializations), +// inline functions, and classes with only inline member functions. Exporting +// classes that inherit from non-exported/imported bases (e.g., std::string) +// will end up badly. The only known workarounds are to not inherit or to not +// export. Also, MinGW GCC doesn't like seeing non-exported functions being +// used before their inline definition. The workaround is to reorder code. In +// the end it's all trial and error. + +#if defined(LIBBUILD2_VERSION_STATIC) // Using static. +# define LIBBUILD2_VERSION_SYMEXPORT +#elif defined(LIBBUILD2_VERSION_STATIC_BUILD) // Building static. +# define LIBBUILD2_VERSION_SYMEXPORT +#elif defined(LIBBUILD2_VERSION_SHARED) // Using shared. +# ifdef _WIN32 +# define LIBBUILD2_VERSION_SYMEXPORT __declspec(dllimport) +# else +# define LIBBUILD2_VERSION_SYMEXPORT +# endif +#elif defined(LIBBUILD2_VERSION_SHARED_BUILD) // Building shared. +# ifdef _WIN32 +# define LIBBUILD2_VERSION_SYMEXPORT __declspec(dllexport) +# else +# define LIBBUILD2_VERSION_SYMEXPORT +# endif +#else +// If none of the above macros are defined, then we assume we are being used +// by some third-party build system that cannot/doesn't signal the library +// type. Note that this fallback works for both static and shared but in case +// of shared will be sub-optimal compared to having dllimport. +// +# define LIBBUILD2_VERSION_SYMEXPORT // Using static or shared. +#endif diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx new file mode 100644 index 0000000..8adbb29 --- /dev/null +++ b/libbuild2/version/init.cxx @@ -0,0 +1,407 @@ +// file : libbuild2/version/init.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace version + { + static const path manifest_file ("manifest"); + + static const in_rule in_rule_; + static const manifest_install_rule manifest_install_rule_; + + bool + boot (scope& rs, const location& l, unique_ptr& mod) + { + tracer trace ("version::boot"); + l5 ([&]{trace << "for " << rs;}); + + // Extract the version from the manifest file. As well as summary and + // url while at it. + // + // Also, as a sanity check, verify the package name matches the build + // system project name. + // + string sum; + string url; + + standard_version v; + dependencies ds; + { + path f (rs.src_path () / manifest_file); + + try + { + if (!file_exists (f)) + fail (l) << "no manifest file in " << rs.src_path (); + + ifdstream ifs (f); + manifest_parser p (ifs, f.string ()); + + manifest_name_value nv (p.next ()); + if (!nv.name.empty () || nv.value != "1") + fail (l) << "unsupported manifest format in " << f; + + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + if (nv.name == "name") + { + auto& pn (cast (rs.vars[var_project])); + + if (nv.value != pn.string ()) + { + path bf (rs.src_path () / rs.root_extra->bootstrap_file); + location ml (&f, nv.value_line, nv.value_column); + location bl (&bf); + + fail (ml) << "package name " << nv.value << " does not match " + << "build system project name " << pn << + info (bl) << "build system project name specified here"; + } + } + if (nv.name == "summary") + sum = move (nv.value); + else if (nv.name == "url") + url = move (nv.value); + else if (nv.name == "version") + { + try + { + // Allow the package stub versions in the 0+ form. + // While not standard, we want to use the version module for + // packaging stubs. + // + v = standard_version (nv.value, standard_version::allow_stub); + } + catch (const invalid_argument& e) + { + fail << "invalid standard version '" << nv.value << "': " << e; + } + } + else if (nv.name == "depends") + { + // According to the package manifest spec, the format of the + // 'depends' value is as follows: + // + // depends: [?][*] [; ] + // + // := [ '|' ]* + // := [] + // := | + // := ('==' | '>' | '<' | '>=' | '<=') + // := ('(' | '[') (')' | ']') + // + // 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); + + try + { + package_name pn (move (n)); + string v (pn.variable ()); + + ds.emplace (move (v), dependency {move (pn), move (c)}); + } + catch (const invalid_argument& e) + { + fail (l) << "invalid package name for dependency " + << d << ": " << e; + } + } + } + } + } + catch (const manifest_parsing& e) + { + location l (&f, e.line, e.column); + fail (l) << e.description; + } + catch (const io_error& e) + { + fail (l) << "unable to read from " << f << ": " << e; + } + catch (const system_error& e) // EACCES, etc. + { + fail (l) << "unable to access manifest " << f << ": " << e; + } + + if (v.empty ()) + fail (l) << "no version in " << f; + } + + // If this is the latest snapshot (i.e., the -a.1.z kind), then load the + // snapshot number and id (e.g., commit date and id from git). + // + bool committed (true); + bool rewritten (false); + if (v.snapshot () && v.snapshot_sn == standard_version::latest_sn) + { + snapshot ss (extract_snapshot (rs)); + + if (!ss.empty ()) + { + v.snapshot_sn = ss.sn; + v.snapshot_id = move (ss.id); + committed = ss.committed; + rewritten = true; + } + else + committed = false; + } + + // If there is a dependency on the build system itself, check it (so + // there is no need for explicit using build@X.Y.Z). + // + { + auto i (ds.find ("build2")); + + if (i != ds.end () && !i->second.constraint.empty ()) + try + { + check_build_version ( + standard_version_constraint (i->second.constraint, v), l); + } + catch (const invalid_argument& e) + { + fail (l) << "invalid version constraint for dependency build2 " + << i->second.constraint << ": " << e; + } + } + + // Set all the version.* variables. + // + auto& vp (var_pool.rw (rs)); + + auto set = [&vp, &rs] (const char* var, auto val) + { + using T = decltype (val); + auto& v (vp.insert (var, variable_visibility::project)); + rs.assign (v) = move (val); + }; + + if (!sum.empty ()) rs.assign (var_project_summary) = move (sum); + if (!url.empty ()) rs.assign (var_project_url) = move (url); + + set ("version", v.string ()); // Project version (var_version). + + set ("version.project", v.string_project ()); + set ("version.project_number", v.version); + + // Enough of project version for unique identification (can be used in + // places like soname, etc). + // + set ("version.project_id", v.string_project_id ()); + + set ("version.stub", v.stub ()); // bool + + set ("version.epoch", uint64_t (v.epoch)); + + set ("version.major", uint64_t (v.major ())); + set ("version.minor", uint64_t (v.minor ())); + set ("version.patch", uint64_t (v.patch ())); + + optional a (v.alpha ()); + optional b (v.beta ()); + + set ("version.alpha", a.has_value ()); + set ("version.beta", b.has_value ()); + set ("version.pre_release", v.pre_release ().has_value ()); + set ("version.pre_release_string", v.string_pre_release ()); + set ("version.pre_release_number", uint64_t (a ? *a : b ? *b : 0)); + + set ("version.snapshot", v.snapshot ()); // bool + set ("version.snapshot_sn", v.snapshot_sn); // uint64 + set ("version.snapshot_id", v.snapshot_id); // string + set ("version.snapshot_string", v.string_snapshot ()); + set ("version.snapshot_committed", committed); // bool + + set ("version.revision", uint64_t (v.revision)); + + // Create the module. + // + mod.reset (new module (cast (rs.vars[var_project]), + move (v), + committed, + rewritten, + move (ds))); + + return true; // Init first (dist.package, etc). + } + + static void + dist_callback (const path&, const scope&, void*); + + bool + init (scope& rs, + scope&, + const location& l, + unique_ptr& mod, + bool first, + bool, + const variable_map&) + { + tracer trace ("version::init"); + + if (!first) + fail (l) << "multiple version module initializations"; + + // Load in.base (in.* variables, in{} target type). + // + if (!cast_false (rs["in.base.loaded"])) + load_module (rs, rs, "in.base", l); + + module& m (static_cast (*mod)); + const standard_version& v (m.version); + + // If the dist module is used, set its dist.package and register the + // post-processing callback. + // + if (auto* dm = rs.lookup_module (dist::module::name)) + { + // Make sure dist is init'ed, not just boot'ed. + // + if (!cast_false (rs["dist.loaded"])) + load_module (rs, rs, "dist", l); + + m.dist_uncommitted = cast_false (rs["config.dist.uncommitted"]); + + // Don't touch if dist.package was set by the user. + // + value& val (rs.assign (dm->var_dist_package)); + + if (!val) + { + string p (cast (rs.vars[var_project]).string ()); + p += '-'; + p += v.string (); + val = move (p); + + // Only register the post-processing callback if this is a rewritten + // snapshot. + // + if (m.rewritten) + dm->register_callback (dir_path (".") / manifest_file, + &dist_callback, + &m); + } + } + + // Register rules. + // + { + auto& r (rs.rules); + + r.insert (perform_update_id, "version.in", in_rule_); + r.insert (perform_clean_id, "version.in", in_rule_); + r.insert (configure_update_id, "version.in", in_rule_); + + if (cast_false (rs["install.booted"])) + { + r.insert ( + perform_install_id, "version.manifest", manifest_install_rule_); + } + } + + return true; + } + + static void + dist_callback (const path& f, const scope& rs, void* data) + { + module& m (*static_cast (data)); + + // Complain if this is an uncommitted snapshot. + // + if (!m.committed && !m.dist_uncommitted) + fail << "distribution of uncommitted project " << rs.src_path () << + info << "specify config.dist.uncommitted=true to force"; + + // The plan is simple: fixing up the version in a temporary file then + // move it to the original. + // + try + { + auto_rmfile t (fixup_manifest (f, + path::temp_path ("manifest"), + m.version)); + + mvfile (t.path, f, (cpflags::overwrite_content | + cpflags::overwrite_permissions)); + t.cancel (); + } + catch (const io_error& e) + { + fail << "unable to overwrite " << f << ": " << e; + } + catch (const system_error& e) // EACCES, etc. + { + fail << "unable to overwrite " << f << ": " << e; + } + } + + static const module_functions mod_functions[] = + { + // NOTE: don't forget to also update the documentation in init.hxx if + // changing anything here. + + {"version", boot, init}, + {nullptr, nullptr, nullptr} + }; + + const module_functions* + build2_version_load () + { + return mod_functions; + } + } +} diff --git a/libbuild2/version/init.hxx b/libbuild2/version/init.hxx new file mode 100644 index 0000000..68e4def --- /dev/null +++ b/libbuild2/version/init.hxx @@ -0,0 +1,28 @@ +// file : libbuild2/version/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_VERSION_INIT_HXX +#define LIBBUILD2_VERSION_INIT_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace version + { + // Module `version` requires bootstrapping. + // + // No submodules. + // + extern "C" LIBBUILD2_VERSION_SYMEXPORT const module_functions* + build2_version_load (); + } +} + +#endif // LIBBUILD2_VERSION_INIT_HXX diff --git a/libbuild2/version/module.cxx b/libbuild2/version/module.cxx new file mode 100644 index 0000000..5ee44f7 --- /dev/null +++ b/libbuild2/version/module.cxx @@ -0,0 +1,15 @@ +// file : libbuild2/version/module.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +using namespace std; + +namespace build2 +{ + namespace version + { + const string module::name ("version"); + } +} diff --git a/libbuild2/version/module.hxx b/libbuild2/version/module.hxx new file mode 100644 index 0000000..174da7b --- /dev/null +++ b/libbuild2/version/module.hxx @@ -0,0 +1,64 @@ +// file : libbuild2/version/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_VERSION_MODULE_HXX +#define LIBBUILD2_VERSION_MODULE_HXX + +#include + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + // A map of package names sanitized for use in variable names to the + // 'depends' values from manifest. + // + using package_name = project_name; + + struct dependency + { + package_name name; + string constraint; + }; + + using dependencies = std::map; + + struct module: module_base + { + using dependencies_type = version::dependencies; + + static const string name; + + // The project variable value sanitized for use in variable names. + // + const string project; + + butl::standard_version version; + bool committed; // Whether this is a committed snapshot. + bool rewritten; // Whether this is a rewritten .z snapshot. + + dependencies_type dependencies; + + bool dist_uncommitted = false; + + module (const project_name& p, + butl::standard_version v, + bool c, + bool r, + dependencies_type d) + : project (p.variable ()), + version (move (v)), + committed (c), + rewritten (r), + dependencies (move (d)) {} + }; + } +} + +#endif // LIBBUILD2_VERSION_MODULE_HXX 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 + +#include +#include +#include + +#include + +#include +#include + +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 () || 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 (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 (); + } + + // 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::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 ()); + + // 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 () || 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::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); + } + } +} diff --git a/libbuild2/version/rule.hxx b/libbuild2/version/rule.hxx new file mode 100644 index 0000000..ce21aa4 --- /dev/null +++ b/libbuild2/version/rule.hxx @@ -0,0 +1,52 @@ +// file : libbuild2/version/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_VERSION_RULE_HXX +#define LIBBUILD2_VERSION_RULE_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace version + { + // Preprocess an .in file that depends on manifest. + // + class in_rule: public in::rule + { + public: + in_rule (): rule ("version.in 2", "version.in") {} + + virtual bool + match (action, target&, const string&) const override; + + virtual string + lookup (const location&, + action, + const target&, + const string&) const override; + }; + + // Pre-process manifest before installation to patch in the version. + // + class manifest_install_rule: public install::file_rule + { + public: + manifest_install_rule () {} + + virtual bool + match (action, target&, const string&) const override; + + virtual auto_rmfile + install_pre (const file&, const install_dir&) const override; + }; + } +} + +#endif // LIBBUILD2_VERSION_RULE_HXX diff --git a/libbuild2/version/snapshot-git.cxx b/libbuild2/version/snapshot-git.cxx new file mode 100644 index 0000000..b7ca084 --- /dev/null +++ b/libbuild2/version/snapshot-git.cxx @@ -0,0 +1,175 @@ +// file : libbuild2/version/snapshot-git.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // time_t + +#include + +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace version + { + snapshot + extract_snapshot_git (const dir_path& src_root) + { + snapshot r; + const char* d (src_root.string ().c_str ()); + + // First check whether the working directory is clean. There doesn't + // seem to be a way to do everything in a single invocation (the + // porcelain v2 gives us the commit id but not timestamp). + // + + // If git status --porcelain returns anything, then the working + // directory is not clean. + // + { + const char* args[] {"git", "-C", d, "status", "--porcelain", nullptr}; + r.committed = run ( + 3 /* verbosity */, + args, + [](string& s, bool) {return move (s);}).empty (); + } + + // Now extract the commit id and date. One might think that would be + // easy... Commit id is a SHA1 hash of the commit object. And commit + // object looks like this: + // + // commit \0 + // + // + // Where is the size of and is the output of: + // + // git cat-file commit HEAD + // + // There is also one annoying special case: new repository without any + // commits. In this case the above command will fail (with diagnostics + // and non-zero exit code) because there is no HEAD. Of course, it can + // also fail for other reason (like broken repository) which would be + // hard to distinguish. Note, however, that we just ran git status and + // it would have most likely failed if this were the case. So here we + // (reluctantly) assume that the only reason git cat-file fails is if + // there is no HEAD (that we equal with the "new repository" condition + // which is, strictly speaking, might not be the case either). So we + // suppress any diagnostics, and handle non-zero exit code. + // + string data; + + const char* args[] { + "git", "-C", d, "cat-file", "commit", "HEAD", nullptr}; + process pr (run_start (3 /* verbosity */, + args, + 0 /* stdin */, + -1 /* stdout */, + false /* error */)); + + string l; + try + { + ifdstream is (move (pr.in_ofd), ifdstream::badbit); + + while (!eof (getline (is, l))) + { + data += l; + data += '\n'; // We assume there is always a newline. + + if (r.sn == 0 && l.compare (0, 10, "committer ") == 0) + try + { + // The line format is: + // + // committer + // + // For example: + // + // committer John Doe 1493117819 +0200 + // + // The timestamp is in seconds since UNIX epoch. The timezone + // appears to be always numeric (+0000 for UTC). Note that + // timestamp appears to be already in UTC with timezone being just + // for information it seems. + // + size_t p1 (l.rfind (' ')); // Can't be npos. + + size_t p2 (l.rfind (' ', p1 - 1)); + if (p2 == string::npos) + throw invalid_argument ("missing timestamp"); + + string ts (l, p2 + 1, p1 - p2 - 1); + time_t t (static_cast (stoull (ts))); + +#if 0 + string tz (l, p1 + 1); + + if (tz.size () != 5) + throw invalid_argument ("invalid timezone"); + + unsigned long h (stoul (string (tz, 1, 2))); + unsigned long m (stoul (string (tz, 3, 2))); + unsigned long s (h * 3600 + m * 60); + + // The timezone indicates where the timestamp was generated so to + // convert to UTC we need to invert the sign. + // + switch (tz[0]) + { + case '+': t -= s; break; + case '-': t += s; break; + default: throw invalid_argument ("invalid timezone sign"); + } +#endif + // Represent as YYYYMMDDhhmmss. + // + r.sn = stoull (to_string (system_clock::from_time_t (t), + "%Y%m%d%H%M%S", + false /* special */, + false /* local (already in UTC) */)); + } + catch (const invalid_argument& e) + { + fail << "unable to extract git commit date from '" << l << "': " + << e; + } + } + + is.close (); + } + catch (const io_error&) + { + // Presumably the child process failed. Let run_finish() deal with + // that. + } + + if (!run_finish (args, pr, false /* error */, l)) + { + // Presumably new repository without HEAD. Return uncommitted snapshot + // with UNIX epoch as timestamp. + // + r.sn = 19700101000000ULL; + r.committed = false; + return r; + } + + if (r.sn == 0) + fail << "unable to extract git commit id/date for " << src_root; + + if (r.committed) + { + sha1 cs; + cs.append ("commit " + to_string (data.size ())); // Includes '\0'. + cs.append (data.c_str (), data.size ()); + r.id.assign (cs.string (), 12); // 12-characters abbreviated commit id. + } + else + r.sn++; // Add a second. + + return r; + } + } +} diff --git a/libbuild2/version/snapshot.cxx b/libbuild2/version/snapshot.cxx new file mode 100644 index 0000000..46b37f3 --- /dev/null +++ b/libbuild2/version/snapshot.cxx @@ -0,0 +1,39 @@ +// file : libbuild2/version/snapshot.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace build2 +{ + namespace version + { + snapshot + extract_snapshot_git (const dir_path&); + + static const path git (".git"); + + snapshot + extract_snapshot (const scope& rs) + { + // Ignore errors when checking for existence since we may be iterating + // over directories past any reasonable project boundaries. + // + for (dir_path d (rs.src_path ()); !d.empty (); d = d.directory ()) + { + // .git can be either a directory or a file in case of a submodule. + // + if (butl::entry_exists (d / git, + true /* follow_symlinks */, + true /* ignore_errors */)) + return extract_snapshot_git (d); + } + + return snapshot (); + } + } +} diff --git a/libbuild2/version/snapshot.hxx b/libbuild2/version/snapshot.hxx new file mode 100644 index 0000000..86b6eab --- /dev/null +++ b/libbuild2/version/snapshot.hxx @@ -0,0 +1,34 @@ +// file : libbuild2/version/snapshot.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_VERSION_SNAPSHOT_HXX +#define LIBBUILD2_VERSION_SNAPSHOT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + struct snapshot + { + uint64_t sn = 0; + string id; + bool committed = false; + + bool + empty () const {return sn == 0;} + }; + + // Return empty snapshot if unknown scm or uncommitted. + // + snapshot + extract_snapshot (const scope& rs); + } +} + +#endif // LIBBUILD2_VERSION_SNAPSHOT_HXX diff --git a/libbuild2/version/utility.cxx b/libbuild2/version/utility.cxx new file mode 100644 index 0000000..c93a251 --- /dev/null +++ b/libbuild2/version/utility.cxx @@ -0,0 +1,81 @@ +// file : libbuild2/version/utility.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +#include + +using namespace butl; + +namespace build2 +{ + namespace version + { + auto_rmfile + fixup_manifest (const path& in, path out, const standard_version& v) + { + auto_rmfile r (move (out), !dry_run /* active */); + + if (!dry_run) + { + try + { + permissions perm (path_permissions (in)); + + ifdstream ifs (in); + manifest_parser p (ifs, in.string ()); + + auto_fd ofd (fdopen (r.path, + fdopen_mode::out | + fdopen_mode::create | + fdopen_mode::exclusive | + fdopen_mode::binary, + perm)); + + ofdstream ofs (move (ofd)); + manifest_serializer s (ofs, r.path.string ()); + + manifest_name_value nv (p.next ()); + assert (nv.name.empty () && nv.value == "1"); // We just loaded it. + s.next (nv.name, nv.value); + + for (nv = p.next (); !nv.empty (); nv = p.next ()) + { + if (nv.name == "version") + nv.value = v.string (); + + s.next (nv.name, nv.value); + } + + s.next (nv.name, nv.value); // End of manifest. + s.next (nv.name, nv.value); // End of stream. + + ofs.close (); + ifs.close (); + } + catch (const manifest_parsing& e) + { + location l (&in, e.line, e.column); + fail (l) << e.description; + } + catch (const manifest_serialization& e) + { + location l (&r.path); + fail (l) << e.description; + } + catch (const io_error& e) + { + fail << "io error: " << e << + info << "while reading " << in << + info << "while writing " << r.path; + } + } + + return r; + } + } +} diff --git a/libbuild2/version/utility.hxx b/libbuild2/version/utility.hxx new file mode 100644 index 0000000..16e8c78 --- /dev/null +++ b/libbuild2/version/utility.hxx @@ -0,0 +1,25 @@ +// file : libbuild2/version/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_VERSION_UTILITY_HXX +#define LIBBUILD2_VERSION_UTILITY_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + // Re-serialize the manifest fixing up the version. Note that this will + // not preserve comments. Probably acceptable for snapshots. + // + auto_rmfile + fixup_manifest (const path& in, path out, const standard_version&); + } +} + +#endif // LIBBUILD2_VERSION_UTILITY_HXX diff --git a/tests/libbuild2/buildfile b/tests/libbuild2/buildfile index 51b2f5b..aa6c17f 100644 --- a/tests/libbuild2/buildfile +++ b/tests/libbuild2/buildfile @@ -4,6 +4,7 @@ import libs = build2%lib{build2} import libs += build2%lib{build2-in} +import libs += build2%lib{build2-version} exe{driver}: {hxx cxx}{*} $libs testscript diff --git a/tests/libbuild2/driver.cxx b/tests/libbuild2/driver.cxx index d9c086a..544d43e 100644 --- a/tests/libbuild2/driver.cxx +++ b/tests/libbuild2/driver.cxx @@ -9,6 +9,7 @@ #include #include +#include using namespace build2; @@ -20,6 +21,7 @@ main (int, char* argv[]) init_diag (1); init (argv[0]); + version::build2_version_load (); in::build2_in_load (); sched.startup (1); // Serial execution. -- cgit v1.1