diff options
Diffstat (limited to 'libbuild2/install/rule.cxx')
-rw-r--r-- | libbuild2/install/rule.cxx | 431 |
1 files changed, 318 insertions, 113 deletions
diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 2d81067..20a4bc3 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -13,6 +13,8 @@ #include <libbuild2/filesystem.hxx> #include <libbuild2/diagnostics.hxx> +#include <libbuild2/install/operation.hxx> + using namespace std; using namespace butl; @@ -37,12 +39,28 @@ namespace build2 return r.simple () && r.string () == "false" ? nullptr : &r; } + // Note that the below rules are called for both install and + // update-for-install. + // + // @@ TODO: we clearly need a module class. + // + static inline const variable& + var_install (const scope& rs) + { + context& ctx (rs.ctx); + + return *rs.root_extra->operations[ + (ctx.current_outer_oif != nullptr + ? ctx.current_outer_oif + : ctx.current_inner_oif)->id].ovar; + } + // alias_rule // const alias_rule alias_rule::instance; bool alias_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match. // @@ -105,7 +123,7 @@ namespace build2 // iterates over all its members. // if (!is) - is = install_scope (t); + is = a.operation () != update_id ? install_scope (t) : nullptr; const target* pt (filter (*is, a, t, i)); if (pt == nullptr) @@ -125,7 +143,7 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*pt)["install"]); + auto l ((*pt)[var_install (*p.scope.root_scope ())]); if (l && cast<path> (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); @@ -136,8 +154,8 @@ namespace build2 // libu{}) then ignore it if there is no rule to install. // if (pt->is_a<file> ()) - build2::match (a, *pt); - else if (!try_match (a, *pt).first) + match_sync (a, *pt); + else if (!try_match_sync (a, *pt).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; @@ -155,7 +173,7 @@ namespace build2 const fsdir_rule fsdir_rule::instance; bool fsdir_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match. // @@ -179,7 +197,7 @@ namespace build2 if (a.operation () == update_id) { match_inner (a, t); - return &execute_inner; + return inner_recipe; } else return noop_recipe; @@ -190,10 +208,10 @@ namespace build2 const group_rule group_rule::instance (false /* see_through_only */); bool group_rule:: - match (action a, target& t, const string& h) const + match (action a, target& t) const { - return (!see_through || t.type ().see_through) && - alias_rule::match (a, t, h); + return (!see_through_only || t.type ().see_through ()) && + alias_rule::match (a, t); } const target* group_rule:: @@ -202,6 +220,25 @@ namespace build2 return &m; } + const target* group_rule:: + filter (const scope* is, + action, const target& t, const prerequisite& p) const + { + // The same logic as in file_rule::filter() below. + // + if (p.is_a<exe> ()) + { + const scope& rs (*p.scope.root_scope ()); + + if (p.vars.empty () || + cast_empty<path> (p.vars[var_install (rs)]).string () != "true") + return nullptr; + } + + const target& pt (search (t, p)); + return is == nullptr || pt.in (*is) ? &pt : nullptr; + } + recipe group_rule:: apply (action a, target& t) const { @@ -211,7 +248,7 @@ namespace build2 // // Remember that we are called twice: first during update for install // (pre-operation) and then during install. During the former, we rely - // on the normall update rule to resolve the group members. During the + // on the normal update rule to resolve the group members. During the // latter, there will be no rule to do this but the group will already // have been resolved by the pre-operation. // @@ -221,8 +258,10 @@ namespace build2 ? resolve_members (a, t) : t.group_members (a)); - if (gv.members != nullptr) + if (gv.members != nullptr && gv.count != 0) { + const scope& rs (t.root_scope ()); + auto& pts (t.prerequisite_targets[a]); for (size_t i (0); i != gv.count; ++i) { @@ -245,14 +284,14 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*mt)["install"]); + auto l ((*mt)[var_install (rs)]); if (l && cast<path> (l).string () == "false") { l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); continue; } - build2::match (a, *mt); + match_sync (a, *mt); pts.push_back (mt); // Never ad hoc. } } @@ -268,7 +307,7 @@ namespace build2 const file_rule file_rule::instance; bool file_rule:: - match (action, target&, const string&) const + match (action, target&) const { // We always match, even if this target is not installable (so that we // can ignore it; see apply()). @@ -288,13 +327,19 @@ namespace build2 filter (const scope* is, action, const target& t, const prerequisite& p) const { + // See also group_rule::filter() with identical semantics. + // if (p.is_a<exe> ()) { - // Feels like one day this should be unified with include (see - // context::var_include). + const scope& rs (*p.scope.root_scope ()); + + // Note that while include() checks for install=false, here we need to + // check for explicit install=true. We could have re-used the lookup + // performed by include(), but then we would have had to drag it + // through and also diagnose any invalid values. // if (p.vars.empty () || - cast_empty<path> (p.vars["install"]).string () != "true") + cast_empty<path> (p.vars[var_install (rs)]).string () != "true") return nullptr; } @@ -306,7 +351,7 @@ namespace build2 apply (action a, target& t) const { recipe r (apply_impl (a, t)); - return r != nullptr ? r : noop_recipe; + return r != nullptr ? move (r) : noop_recipe; } recipe file_rule:: @@ -366,7 +411,7 @@ namespace build2 // iterates over all its members. // if (!is) - is = install_scope (t); + is = a.operation () != update_id ? install_scope (t) : nullptr; const target* pt (filter (*is, a, t, i)); @@ -381,7 +426,7 @@ namespace build2 // // Note: not the same as lookup_install() above. // - auto l ((*pt)["install"]); + auto l ((*pt)[var_install (*p.scope.root_scope ())]); if (l && cast<path> (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); @@ -396,10 +441,10 @@ namespace build2 // when updating static installable content (headers, documentation, // etc). // - if (build2::match (a, *pt, unmatch::unchanged).first) + if (match_sync (a, *pt, unmatch::unchanged).first) pt = nullptr; } - else if (!try_match (a, *pt).first) + else if (!try_match_sync (a, *pt).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; @@ -510,7 +555,8 @@ namespace build2 const dir_path& d (t.out_dir ().leaf (p->out_path ())); // Add it as another leading directory rather than modifying - // the last one directly; somehow, it feels right. + // the last one directly; somehow, it feels right. Note: the + // result is normalized. // if (!d.empty ()) rs.emplace_back (rs.back ().dir / d, rs.back ()); @@ -521,8 +567,9 @@ namespace build2 return rs.back (); } - // Resolve installation directory name to absolute directory path. Return - // all the super-directories leading up to the destination (last). + // Resolve installation directory name to absolute and normalized + // directory path. Return all the super-directories leading up to the + // destination (last). // // If target is not NULL, then also handle the subdirs logic. // @@ -621,24 +668,52 @@ namespace build2 return rs; } - static inline install_dirs - resolve (const target& t, dir_path d, bool fail_unknown = true) + static dir_path + resolve_dir (const scope& s, const target* t, + dir_path d, dir_path rb, + bool fail_unknown) { - return resolve (t.base_scope (), &t, move (d), fail_unknown); + install_dirs rs (resolve (s, t, move (d), fail_unknown)); + + if (rs.empty ()) + return dir_path (); + + dir_path r (move (rs.back ().dir)); + + if (!rb.empty ()) + { + dir_path b (resolve (s, t, move (rb), false).back ().dir); + + try + { + r = r.relative (b); + } + catch (const invalid_path&) + { + fail << "unable to make installation directory " << r + << " relative to " << b; + } + } + + return r; } dir_path - resolve_dir (const target& t, dir_path d, bool fail_unknown) + resolve_dir (const target& t, dir_path d, dir_path rb, bool fail_unknown) { - install_dirs r (resolve (t, move (d), fail_unknown)); - return r.empty () ? dir_path () : move (r.back ().dir); + return resolve_dir (t.base_scope (), &t, move (d), move (rb), fail_unknown); } dir_path - resolve_dir (const scope& s, dir_path d, bool fail_unknown) + resolve_dir (const scope& s, dir_path d, dir_path rb, bool fail_unknown) { - install_dirs r (resolve (s, nullptr, move (d), fail_unknown)); - return r.empty () ? dir_path () : move (r.back ().dir); + return resolve_dir (s, nullptr, move (d), move (rb), fail_unknown); + } + + static inline install_dirs + resolve (const target& t, dir_path d, bool fail_unknown = true) + { + return resolve (t.base_scope (), &t, move (d), fail_unknown); } path @@ -654,6 +729,10 @@ namespace build2 bool n (!p->to_directory ()); dir_path d (n ? p->directory () : path_cast<dir_path> (*p)); + if (n && d.empty ()) + fail << "relative installation file path '" << p + << "' has no directory component"; + install_dirs ids (resolve (f, d)); if (!n) @@ -704,30 +783,15 @@ namespace build2 return s; } - // Given an abolute path return its chroot'ed version, if any, accoring to - // install.chroot. - // - template <typename P> - static inline P - chroot_path (const scope& rs, const P& p) - { - if (const dir_path* d = cast_null<dir_path> (rs["install.chroot"])) - { - dir_path r (p.root_directory ()); - assert (!r.empty ()); // Must be absolute. - - return *d / p.leaf (r); - } - - return p; - } - void file_rule:: install_d (const scope& rs, const install_dir& base, const dir_path& d, + const file& t, uint16_t verbosity) { + assert (d.absolute ()); + context& ctx (rs.ctx); // Here is the problem: if this is a dry-run, then we will keep showing @@ -740,7 +804,10 @@ namespace build2 // with uninstall since the directories won't be empty (because we don't // actually uninstall any files). // - if (ctx.dry_run) + // Note that this also means we won't have the directory entries in the + // manifest created with dry-run. Probably not a big deal. + // + if (ctx.dry_run || !filter_entry (rs, d, path (), entry_type::directory)) return; dir_path chd (chroot_path (rs, d)); @@ -767,13 +834,13 @@ namespace build2 dir_path pd (d.directory ()); if (pd != base.dir) - install_d (rs, base, pd, verbosity); + install_d (rs, base, pd, t, verbosity); } cstrings args; string reld ( - cast<string> (ctx.global_scope["build.host.class"]) == "windows" + ctx.build_host->class_ == "windows" ? msys_path (chd) : relative (chd).string ()); @@ -798,10 +865,14 @@ namespace build2 if (verb >= 2) print_process (args); else if (verb) - text << "install " << chd; + print_diag ("install -d", chd); // See also `install -l` below. } - run (pp, args); + run (ctx, + pp, args, + verb >= verbosity ? 1 : verb_never /* finish_verbosity */); + + context_data::manifest_install_d (ctx, t, d, *base.dir_mode); } void file_rule:: @@ -812,14 +883,21 @@ namespace build2 const path& f, uint16_t verbosity) { + assert (name.empty () || name.simple ()); + context& ctx (rs.ctx); + const path& leaf (name.empty () ? f.leaf () : name); + + if (!filter_entry (rs, base.dir, leaf, entry_type::regular)) + return; + path relf (relative (f)); dir_path chd (chroot_path (rs, base.dir)); string reld ( - cast<string> (ctx.global_scope["build.host.class"]) == "windows" + ctx.build_host->class_ == "windows" ? msys_path (chd) : relative (chd).string ()); @@ -852,23 +930,47 @@ namespace build2 if (verb >= 2) print_process (args); else if (verb) - text << "install " << t; + { + if (name.empty ()) + print_diag ("install", t, chd); + else + print_diag ("install", t, chd / name); + } } if (!ctx.dry_run) - run (pp, args); + run (ctx, + pp, args, + verb >= verbosity ? 1 : verb_never /* finish_verbosity */); + + context_data::manifest_install_f (ctx, t, base.dir, leaf, *base.mode); } void file_rule:: install_l (const scope& rs, const install_dir& base, - const path& target, const path& link, + const file& target, + const path& link_target, uint16_t verbosity) { + assert (link.simple () && !link.empty ()); + context& ctx (rs.ctx); - path rell (relative (chroot_path (rs, base.dir))); + if (!filter_entry (rs, base.dir, link, entry_type::symlink)) + return; + + if (link_target.absolute () && + cast_false<bool> (rs["install.relocatable"])) + { + fail << "absolute symlink target " << link_target.string () + << " in relocatable installation"; + } + + dir_path chd (chroot_path (rs, base.dir)); + + path rell (relative (chd)); rell /= link; // We can create a symlink directly without calling ln. This, however, @@ -882,7 +984,7 @@ namespace build2 base.sudo != nullptr ? base.sudo->c_str () : nullptr, "ln", "-sf", - target.string ().c_str (), + link_target.string ().c_str (), rell.string ().c_str (), nullptr}; @@ -895,11 +997,19 @@ namespace build2 if (verb >= 2) print_process (args); else if (verb) - text << "install " << rell << " -> " << target; + { + // Without a flag it's unclear (unlike with ln) that we are creating + // a link. FreeBSD install(1) has the -l flag with the appropriate + // semantics. For consistency, we also pass -d above. + // + print_diag ("install -l", link_target, chd / link); + } } if (!ctx.dry_run) - run (pp, args); + run (ctx, + pp, args, + verb >= verbosity ? 1 : verb_never /* finish_verbosity */); #else // The -f part. // @@ -911,15 +1021,15 @@ namespace build2 if (verb >= verbosity) { if (verb >= 2) - text << "ln -sf " << target.string () << ' ' << rell.string (); + text << "ln -sf " << link_target.string () << ' ' << rell.string (); else if (verb) - text << "install " << rell << " -> " << target; + print_diag ("install -l", link_target, chd / link); } if (!ctx.dry_run) try { - mkanylink (target, rell, true /* copy */); + mkanylink (link_target, rell, true /* copy */); } catch (const pair<entry_type, system_error>& e) { @@ -931,6 +1041,12 @@ namespace build2 fail << "unable to make " << w << ' ' << rell << ": " << e.second; } #endif + + context_data::manifest_install_l (ctx, + target, + link_target, + base.dir, + link); } target_state file_rule:: @@ -954,6 +1070,10 @@ namespace build2 bool n (!p.to_directory ()); dir_path d (n ? p.directory () : path_cast<dir_path> (p)); + if (n && d.empty ()) + fail << "relative installation file path '" << p + << "' has no directory component"; + // Resolve target directory. // install_dirs ids (resolve (t, d)); @@ -975,7 +1095,7 @@ namespace build2 // sudo, etc). // for (auto i (ids.begin ()), j (i); i != ids.end (); j = i++) - install_d (rs, *j, i->dir, verbosity); // install -d + install_d (rs, *j, i->dir, t, verbosity); // install -d install_dir& id (ids.back ()); @@ -1033,7 +1153,7 @@ namespace build2 // if (!tp.empty ()) { - install_target (t, cast<path> (t["install"]), 1); + install_target (t, cast<path> (t[var_install (rs)]), 1); r |= target_state::changed; } @@ -1046,9 +1166,13 @@ namespace build2 const dir_path& d, uint16_t verbosity) { + assert (d.absolute ()); + + context& ctx (rs.ctx); + // See install_d() for the rationale. // - if (rs.ctx.dry_run) + if (ctx.dry_run || !filter_entry (rs, d, path (), entry_type::directory)) return false; dir_path chd (chroot_path (rs, d)); @@ -1095,7 +1219,7 @@ namespace build2 if (verb >= 2) text << "rmdir " << reld; else if (verb) - text << "uninstall " << reld; + print_diag ("uninstall -d", chd); } try @@ -1125,11 +1249,19 @@ namespace build2 if (verb >= 2) print_process (args); else if (verb) - text << "uninstall " << reld; + print_diag ("uninstall -d", chd); } - process pr (run_start (pp, args)); - r = run_finish_code (args, pr); + process pr (run_start (pp, args, + 0 /* stdin */, + 1 /* stdout */, + diag_buffer::pipe (ctx) /* stderr */)); + diag_buffer dbuf (ctx, args[0], pr); + dbuf.read (); + r = run_finish_code ( + dbuf, + args, pr, + verb >= verbosity ? 1 : verb_never /* verbosity */); } if (!r) @@ -1153,40 +1285,16 @@ namespace build2 return r; } - bool file_rule:: - uninstall_f (const scope& rs, - const install_dir& base, - const file* t, - const path& name, - uint16_t verbosity) + static void + uninstall_f_impl (const scope& rs, + const install_dir& base, + const path& f, + uint16_t verbosity) { - assert (t != nullptr || !name.empty ()); - path f (chroot_path (rs, base.dir) / - (name.empty () ? t->path ().leaf () : name)); - - try - { - // Note: don't follow symlinks so if the target is a dangling symlinks - // we will proceed to removing it. - // - if (!file_exists (f, false)) // May throw (e.g., EACCES). - return false; - } - catch (const system_error& e) - { - fail << "invalid installation path " << f << ": " << e; - } + context& ctx (rs.ctx); path relf (relative (f)); - if (verb >= verbosity && verb == 1) - { - if (t != nullptr) - text << "uninstall " << *t; - else - text << "uninstall " << relf; - } - // The same story as with uninstall -d (on Windows rm is also from // MSYS2/Cygwin). // @@ -1196,7 +1304,7 @@ namespace build2 if (verb >= verbosity && verb >= 2) text << "rm " << relf; - if (!rs.ctx.dry_run) + if (!ctx.dry_run) { try { @@ -1222,13 +1330,107 @@ namespace build2 process_path pp (run_search (args[0])); - if (verb >= verbosity && verb >= 2) - print_process (args); + if (verb >= verbosity) + { + if (verb >= 2) + print_process (args); + } + + if (!ctx.dry_run) + run (ctx, + pp, args, + verb >= verbosity ? 1 : verb_never /* finish_verbosity */); + } + } + + bool file_rule:: + uninstall_f (const scope& rs, + const install_dir& base, + const file* t, + const path& name, + uint16_t verbosity) + { + assert (name.empty () ? t != nullptr : name.simple ()); + + const path& leaf (name.empty () ? t->path ().leaf () : name); + + if (!filter_entry (rs, base.dir, leaf, entry_type::regular)) + return false; + + dir_path chd (chroot_path (rs, base.dir)); + path f (chd / leaf); + + try + { + // Note: don't follow symlinks so if the target is a dangling symlinks + // we will proceed to removing it. + // + if (!file_exists (f, false)) // May throw (e.g., EACCES). + return false; + } + catch (const system_error& e) + { + fail << "invalid installation path " << f << ": " << e; + } + + if (verb >= verbosity && verb == 1) + { + if (t != nullptr) + { + if (name.empty ()) + print_diag ("uninstall", *t, chd, "<-"); + else + print_diag ("uninstall", *t, f, "<-"); + } + else + print_diag ("uninstall", f); + } + + uninstall_f_impl (rs, base, f, verbosity); + return true; + } + + bool file_rule:: + uninstall_l (const scope& rs, + const install_dir& base, + const path& link, + const path& /*link_target*/, + uint16_t verbosity) + { + assert (link.simple () && !link.empty ()); + + if (!filter_entry (rs, base.dir, link, entry_type::symlink)) + return false; + + dir_path chd (chroot_path (rs, base.dir)); + path f (chd / link); - if (!rs.ctx.dry_run) - run (pp, args); + try + { + // Note: don't follow symlinks so if the target is a dangling symlinks + // we will proceed to removing it. + // + if (!file_exists (f, false)) // May throw (e.g., EACCES). + return false; + } + catch (const system_error& e) + { + fail << "invalid installation path " << f << ": " << e; } + if (verb >= verbosity && verb == 1) + { + // It's dubious showing the link target path adds anything useful + // here. + // +#if 0 + print_diag ("uninstall -l", target, f, "<-"); +#else + print_diag ("uninstall -l", f); +#endif + } + + uninstall_f_impl (rs, base, f, verbosity); return true; } @@ -1251,6 +1453,10 @@ namespace build2 bool n (!p.to_directory ()); dir_path d (n ? p.directory () : path_cast<dir_path> (p)); + if (n && d.empty ()) + fail << "relative installation file path '" << p + << "' has no directory component"; + // Resolve target directory. // install_dirs ids (resolve (t, d)); @@ -1298,7 +1504,7 @@ namespace build2 target_state r (target_state::unchanged); if (!tp.empty ()) - r |= uninstall_target (t, cast<path> (t["install"]), 1); + r |= uninstall_target (t, cast<path> (t[var_install (rs)]), 1); // Then installable ad hoc group members, if any. To be anally precise, // we would have to do it in reverse, but that's not easy (it's a @@ -1323,7 +1529,6 @@ namespace build2 } } - // Finally handle installable prerequisites. // r |= reverse_execute_prerequisites (a, t); |