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 --- libbuild2/version/init.cxx | 407 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 libbuild2/version/init.cxx (limited to 'libbuild2/version/init.cxx') 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; + } + } +} -- cgit v1.1