diff options
Diffstat (limited to 'libbuild2/version')
-rw-r--r-- | libbuild2/version/buildfile | 8 | ||||
-rw-r--r-- | libbuild2/version/init.cxx | 216 | ||||
-rw-r--r-- | libbuild2/version/module.hxx | 7 | ||||
-rw-r--r-- | libbuild2/version/rule.cxx | 75 | ||||
-rw-r--r-- | libbuild2/version/rule.hxx | 14 | ||||
-rw-r--r-- | libbuild2/version/snapshot-git.cxx | 69 | ||||
-rw-r--r-- | libbuild2/version/snapshot.cxx | 27 | ||||
-rw-r--r-- | libbuild2/version/utility.cxx | 7 |
8 files changed, 271 insertions, 152 deletions
diff --git a/libbuild2/version/buildfile b/libbuild2/version/buildfile index b991f73..4808ded 100644 --- a/libbuild2/version/buildfile +++ b/libbuild2/version/buildfile @@ -4,14 +4,14 @@ # NOTE: shared imports should go into root.build. # include ../ -imp_libs = ../lib{build2} # Implied interface dependency. +impl_libs = ../lib{build2} # Implied interface dependency. include ../in/ -int_libs = ../in/lib{build2-in} +intf_libs = ../in/lib{build2-in} ./: lib{build2-version}: libul{build2-version}: \ {hxx ixx txx cxx}{** -**.test...} \ - $int_libs $imp_libs + $intf_libs $impl_libs # Unit tests. # @@ -40,7 +40,7 @@ objs{*}: cxx.poptions += -DLIBBUILD2_VERSION_SHARED_BUILD lib{build2-version}: { cxx.export.poptions = "-I$out_root" "-I$src_root" - cxx.export.libs = $int_libs + cxx.export.libs = $intf_libs } liba{build2-version}: cxx.export.poptions += -DLIBBUILD2_VERSION_STATIC diff --git a/libbuild2/version/init.cxx b/libbuild2/version/init.cxx index d11b2f3..b3657bc 100644 --- a/libbuild2/version/init.cxx +++ b/libbuild2/version/init.cxx @@ -3,7 +3,9 @@ #include <libbuild2/version/init.hxx> -#include <libbutl/manifest-parser.mxx> +#include <cstring> // strchr() + +#include <libbutl/manifest-parser.hxx> #include <libbuild2/scope.hxx> #include <libbuild2/variable.hxx> @@ -30,6 +32,44 @@ namespace build2 static const in_rule in_rule_; static const manifest_install_rule manifest_install_rule_; + static void + dist_callback (const path&, const scope&, void*); + + void + boot_post (scope& rs, const location&, module_boot_post_extra& extra) + { + // If the dist module is used, set its dist.package and register the + // post-processing callback. + // + if (auto* dm = rs.find_module<dist::module> (dist::module::name)) + { + // Don't touch if dist.package was set by the user. + // + value& val (rs.assign (dm->var_dist_package)); + + if (!val) + { + auto& m (extra.module_as<module> ()); + const standard_version& v (m.version); + + // We've already verified in boot() it is named. + // + string p (project (rs).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); + } + } + } + void boot (scope& rs, const location& l, module_boot_extra& extra) { @@ -105,61 +145,98 @@ namespace build2 } 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; + // Parse the dependency and add it to the map (see + // bpkg::dependency_alternatives class for dependency syntax). + // + // Note that currently we only consider simple dependencies: + // singe package without alternatives, clauses, or newlines. + // In the future, if/when we add full support, we will likely + // keep this as a fast path. + // + // Also note that we don't do exhaustive validation here leaving + // it to the package manager. // Get rid of the comment. // + // Note that we can potentially mis-detect the comment + // separator, since ';' can be a part of some of the dependency + // alternative clauses. If that's the case, we will skip the + // dependency later. + // + size_t p; 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. + // Skip the dependency if it is not a simple one. + // + // Note that we will check for the presence of the reflect + // clause later since `=` can also be in the constraint. // - if ((p = v.find_last_of ("?*")) != string::npos) - v.erase (0, p + 1); + if (v.find_first_of ("{?|\n") != string::npos) + continue; - // Parse as |-separated "words". + // Find the beginning of the dependency package name, skipping + // the build-time marker, if present. // - for (size_t b (0), e (0); next_word (v, b, e, '|'); ) + bool buildtime (v[0] == '*'); + size_t b (buildtime ? v.find_first_not_of (" \t", 1) : 0); + + if (b == string::npos) + fail (l) << "invalid dependency " << v << ": no package name"; + + // Find the end of the dependency package name. + // + p = v.find_first_of (" \t=<>[(~^", b); + + // Dependency name (without leading/trailing white-spaces). + // + string n (v, b, p == string::npos ? p : p - b); + + string vc; // Empty if no constraint is specified + + // Position to the first non-whitespace character after the + // dependency name, which, if present, can be a part of the + // version constraint or the reflect clause. + // + if (p != string::npos) + p = v.find_first_not_of (" \t", p); + + if (p != string::npos) + { + // Check if this is definitely not a version constraint and + // drop this dependency if that's the case. + // + if (strchr ("=<>[(~^", v[p]) == nullptr) + continue; + + // Ok, we have a constraint, check that there is no reflect + // clause after it (the only other valid `=` in a constraint + // is in the immediately following character as part of + // `==`, `<=`, or `>=`). + // + if (v.size () > p + 2 && v.find ('=', p + 2) != string::npos) + continue; + + vc.assign (v, p, string::npos); + trim (vc); + } + + // Finally, add the dependency to the map. + // + try + { + package_name pn (move (n)); + string v (pn.variable ()); + + ds.emplace (move (v), + dependency {move (pn), move (vc), buildtime}); + } + catch (const invalid_argument& 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; - } + fail (l) << "invalid dependency package name '" << n << "': " + << e; } } } @@ -208,7 +285,9 @@ namespace build2 { auto i (ds.find ("build2")); - if (i != ds.end () && !i->second.constraint.empty ()) + if (i != ds.end () && + i->second.buildtime && + !i->second.constraint.empty ()) try { check_build_version ( @@ -281,19 +360,17 @@ namespace build2 // Initialize second (dist.package, etc). // + extra.post = &boot_post; extra.init = module_boot_init::before_second; } - static void - dist_callback (const path&, const scope&, void*); - bool init (scope& rs, scope&, const location& l, bool first, bool, - module_init_extra& extra) + module_init_extra&) { tracer trace ("version::init"); @@ -304,43 +381,6 @@ namespace build2 // load_module (rs, rs, "in.base", l); - auto& m (extra.module_as<module> ()); - 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.find_module<dist::module> (dist::module::name)) - { - // Make sure dist is init'ed, not just boot'ed. - // - load_module (rs, rs, "dist", l); - - m.dist_uncommitted = cast_false<bool> (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) - { - // We've already verified in boot() it is named. - // - string p (project (rs).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. // rs.insert_rule<file> (perform_update_id, "version.in", in_rule_); @@ -350,7 +390,7 @@ namespace build2 if (cast_false<bool> (rs["install.booted"])) { rs.insert_rule<manifest> ( - perform_install_id, "version.manifest", manifest_install_rule_); + perform_install_id, "version.install", manifest_install_rule_); } return true; @@ -363,7 +403,7 @@ namespace build2 // Complain if this is an uncommitted snapshot. // - if (!m.committed && !m.dist_uncommitted) + if (!m.committed && !cast_false<bool> (rs["config.dist.uncommitted"])) fail << "distribution of uncommitted project " << rs.src_path () << info << "specify config.dist.uncommitted=true to force"; diff --git a/libbuild2/version/module.hxx b/libbuild2/version/module.hxx index bfa7d8c..8549e03 100644 --- a/libbuild2/version/module.hxx +++ b/libbuild2/version/module.hxx @@ -4,8 +4,6 @@ #ifndef LIBBUILD2_VERSION_MODULE_HXX #define LIBBUILD2_VERSION_MODULE_HXX -#include <map> - #include <libbuild2/types.hxx> #include <libbuild2/utility.hxx> @@ -24,9 +22,10 @@ namespace build2 { package_name name; string constraint; + bool buildtime; }; - using dependencies = std::map<string, dependency>; + using dependencies = map<string, dependency>; struct module: build2::module { @@ -44,8 +43,6 @@ namespace build2 dependencies_type dependencies; - bool dist_uncommitted = false; - module (const project_name& p, butl::standard_version v, bool c, diff --git a/libbuild2/version/rule.cxx b/libbuild2/version/rule.cxx index 6fd97ae..65c1117 100644 --- a/libbuild2/version/rule.cxx +++ b/libbuild2/version/rule.cxx @@ -46,12 +46,31 @@ namespace build2 // in_rule // + + // Wrap the in::rule's perform_update recipe into a data-carrying recipe. + // + // To optimize this a bit further (i.e., to avoid the dynamic memory + // allocation) we are going to call in::rule::perform_update() directly + // (after all it's virtual and thus part of the in_rule's interface). + // + struct match_data + { + const module& mod; + const in_rule& rule; + + target_state + operator() (action a, const target& t) + { + return rule.perform_update (a, t); + } + }; + bool in_rule:: - match (action a, target& xt, const string&) const + match (action a, target& xt) const { tracer trace ("version::in_rule::match"); - file& t (static_cast<file&> (xt)); + file& t (xt.as<file> ()); const scope& rs (t.root_scope ()); bool fm (false); // Found manifest. @@ -74,26 +93,43 @@ namespace build2 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 we match, derive the file name early as recommended by the in + // rule. // - if (r && a == perform_update_id) - t.data (rs.find_module<module> (module::name)); + if (fm && fi) + t.derive_path (); - return r; + return fm && fi; + } + + recipe in_rule:: + apply (action a, target& t) const + { + recipe r (rule::apply (a, t)); + + // Lookup and cache the module for the update operation. + // + return a == perform_update_id + ? match_data {*t.root_scope ().find_module<module> (module::name), + *this} + : move (r); } string in_rule:: lookup (const location& l, action a, const target& t, - const string& n) const + const string& n, + optional<uint64_t> flags, + const substitution_map* smap, + const optional<string>& null) const { + assert (!flags); + // Note that this code will be executed during up-to-date check for each // substitution so let's try not to do anything overly sub-optimal here. // - const module& m (*t.data<const module*> ()); + const module& m (t.data<match_data> (a).mod); // Split it into the package name and the variable/condition name. // @@ -108,7 +144,8 @@ namespace build2 return rule::lookup (l, // Standard lookup. a, t, - p == string::npos ? n : string (n, p + 1)); + p == string::npos ? n : string (n, p + 1), + nullopt, smap, null); } string pn (n, 0, p); @@ -210,13 +247,13 @@ namespace build2 if (mav->snapshot ()) { - r += (p ? "(" : ""); + if (p) r += '('; r += cmp (vm, " < ", mav->version) + " || ("; r += cmp (vm, " == ", mav->version) + " && "; - r += cmp (sm, (mao ? " < " : " <= "), mav->snapshot_sn) + ")"; + r += cmp (sm, (mao ? " < " : " <= "), mav->snapshot_sn) + ')'; - r += (p ? ")" : ""); + if (p) r += ')'; } else r = cmp (vm, (mao ? " < " : " <= "), mav->version); @@ -230,13 +267,13 @@ namespace build2 if (miv->snapshot ()) { - r += (p ? "(" : ""); + if (p) r += '('; r += cmp (vm, " > ", miv->version) + " || ("; r += cmp (vm, " == ", miv->version) + " && "; - r += cmp (sm, (mio ? " > " : " >= "), miv->snapshot_sn) + ")"; + r += cmp (sm, (mio ? " > " : " >= "), miv->snapshot_sn) + ')'; - r += (p ? ")" : ""); + if (p) r += ')'; } else r = cmp (vm, (mio ? " > " : " >= "), miv->version); @@ -296,7 +333,7 @@ namespace build2 // manifest_install_rule // bool manifest_install_rule:: - match (action a, target& t, const string&) const + match (action a, target& t) const { // We only match project's manifest. // @@ -309,7 +346,7 @@ namespace build2 if (s.root_scope () != &s || s.src_path () != t.dir) return false; - return file_rule::match (a, t, ""); + return file_rule::match (a, t); } auto_rmfile manifest_install_rule:: diff --git a/libbuild2/version/rule.hxx b/libbuild2/version/rule.hxx index f9e7655..0bdc090 100644 --- a/libbuild2/version/rule.hxx +++ b/libbuild2/version/rule.hxx @@ -20,16 +20,22 @@ namespace build2 class in_rule: public in::rule { public: - in_rule (): rule ("version.in 2", "version.in") {} + in_rule (): rule ("version.in 2", "version") {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; + + virtual recipe + apply (action, target&) const override; virtual string lookup (const location&, action, const target&, - const string&) const override; + const string&, + optional<uint64_t>, + const substitution_map*, + const optional<string>&) const override; }; // Pre-process manifest before installation to patch in the version. @@ -40,7 +46,7 @@ namespace build2 manifest_install_rule () {} virtual bool - match (action, target&, const string&) const override; + match (action, target&) const override; virtual auto_rmfile install_pre (const file&, const install_dir&) const override; diff --git a/libbuild2/version/snapshot-git.cxx b/libbuild2/version/snapshot-git.cxx index 8eb7cc8..ab0224a 100644 --- a/libbuild2/version/snapshot-git.cxx +++ b/libbuild2/version/snapshot-git.cxx @@ -3,7 +3,7 @@ #include <ctime> // time_t -#include <libbutl/sha1.mxx> +#include <libbutl/sha1.hxx> #include <libbuild2/version/snapshot.hxx> @@ -14,11 +14,20 @@ namespace build2 { namespace version { + // We have to run git twice to extract the information we need and doing + // it repetitively is quite expensive, especially for larger repositories. + // So we cache it, which helps multi-package repositories. + // + static global_cache<snapshot, dir_path> cache; + snapshot - extract_snapshot_git (const dir_path& src_root) + extract_snapshot_git (context& ctx, dir_path rep_root) { + if (const snapshot* r = cache.find (rep_root)) + return *r; + snapshot r; - const char* d (src_root.string ().c_str ()); + const char* d (rep_root.string ().c_str ()); // On startup git prepends the PATH environment variable value with the // computed directory path where its sub-programs are supposedly located @@ -37,6 +46,12 @@ namespace build2 // prevent this we pass the git's exec directory via the --exec-path // option explicitly. // + // Note also that git has quite a few GIT_* environment variables and + // stray values for some of them could break our commands. So it may + // seem like a good idea to unset them. But on the other hand, they may + // be there for a reason: after all, we are operating on user's projects + // and user's environment may be setup to handle them. + // path p ("git"); process_path pp (run_search (p, true /* init */)); @@ -67,7 +82,11 @@ namespace build2 args[args_i + 1] = "--porcelain"; args[args_i + 2] = nullptr; + // @@ PERF: redo with custom stream reading code (then could also + // get rid of context). + // r.committed = run<string> ( + ctx, 3 /* verbosity */, pp, args, @@ -93,7 +112,8 @@ namespace build2 // (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. + // suppress any diagnostics, and handle non-zero exit code (and so no + // diagnostics buffering is needed, plus we are in the load phase). // string data; @@ -102,12 +122,12 @@ namespace build2 args[args_i + 2] = "HEAD"; args[args_i + 3] = nullptr; - process pr (run_start (3 /* verbosity */, + process pr (run_start (3 /* verbosity */, pp, args, - 0 /* stdin */, - -1 /* stdout */, - false /* error */)); + 0 /* stdin */, + -1 /* stdout */, + 1 /* stderr (to stdout) */)); string l; try @@ -186,30 +206,31 @@ namespace build2 // that. } - if (!run_finish_code (args, pr, l)) + if (run_finish_code (args, pr, l, 2 /* verbosity */)) + { + if (r.sn == 0) + fail << "unable to extract git commit id/date for " << rep_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-char abbreviated commit id. + } + else + r.sn++; // Add a second. + } + else { // 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; + return cache.insert (move (rep_root), move (r)); } } } diff --git a/libbuild2/version/snapshot.cxx b/libbuild2/version/snapshot.cxx index 9234460..000bcba 100644 --- a/libbuild2/version/snapshot.cxx +++ b/libbuild2/version/snapshot.cxx @@ -12,24 +12,41 @@ namespace build2 namespace version { snapshot - extract_snapshot_git (const dir_path&); + extract_snapshot_git (context&, 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. + // Resolve the path symlink components to make sure that if we are + // extracting snapshot for a subproject which is symlinked from the git + // submodule, then we end up with a root of the git submodule repository + // rather than the containing repository root. // - for (dir_path d (rs.src_path ()); !d.empty (); d = d.directory ()) + dir_path d (rs.src_path ()); + + try + { + d.realize (); + } + catch (const invalid_path&) // Some component doesn't exist. + { + return snapshot (); + } + catch (const system_error& e) + { + fail << "unable to obtain real path for " << d << ": " << e; + } + + for (; !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 extract_snapshot_git (rs.ctx, move (d)); } return snapshot (); diff --git a/libbuild2/version/utility.cxx b/libbuild2/version/utility.cxx index 4b958c6..9fec7ec 100644 --- a/libbuild2/version/utility.cxx +++ b/libbuild2/version/utility.cxx @@ -3,10 +3,11 @@ #include <libbuild2/version/utility.hxx> -#include <libbutl/manifest-parser.mxx> -#include <libbutl/manifest-serializer.mxx> +#include <libbutl/manifest-parser.hxx> +#include <libbutl/manifest-serializer.hxx> #include <libbuild2/context.hxx> +#include <libbuild2/filesystem.hxx> // path_perms() #include <libbuild2/diagnostics.hxx> using namespace butl; @@ -27,7 +28,7 @@ namespace build2 { try { - permissions perm (path_permissions (in)); + permissions perm (path_perms (in)); ifdstream ifs (in); manifest_parser p (ifs, in.string ()); |