// file : libbuild2/install/rule.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include // resolve_dir() declaration #include // dir_exists(), file_exists() #include #include #include #include #include #include #include using namespace std; using namespace butl; namespace build2 { namespace install { // Lookup the install or install.* variable. Return NULL if not found or // if the value is the special 'false' name (which means do not install; // so the result can be used as bool). T is either scope or target. // template static const P* lookup_install (T& t, const string& var) { auto l (t[var]); if (!l) return nullptr; const P& r (cast

(l)); 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 { // We always match. // // Note that we are called both as the outer part during the update-for- // un/install pre-operation and as the inner part during the un/install // operation itself. // return true; } pair alias_rule:: filter (const scope* is, action a, const target& t, prerequisite_iterator& i, match_extra& me) const { assert (i->member == nullptr); return filter (is, a, t, i->prerequisite, me); } pair alias_rule:: filter (const scope* is, action, const target& t, const prerequisite& p, match_extra&) const { const target& pt (search (t, p)); const uint64_t options (match_extra::all_options); // No definition. return make_pair (is == nullptr || pt.in (*is) ? &pt : nullptr, options); } recipe alias_rule:: apply (action a, target& t, match_extra& me) const { return apply_impl (a, t, me); } recipe alias_rule:: apply (action, target&) const { assert (false); // Never called. return nullptr; } recipe alias_rule:: apply_impl (action a, target& t, match_extra& me, bool reapply) const { tracer trace ("install::alias_rule::apply"); assert (!reapply || a.operation () != update_id); // Pass-through to our installable prerequisites. // // @@ Shouldn't we do match in parallel (here and below)? // optional is; // Installation scope (resolve lazily). auto& pts (t.prerequisite_targets[a]); auto pms (group_prerequisite_members (a, t, members_mode::never)); for (auto i (pms.begin ()), e (pms.end ()); i != e; ++i) { // NOTE: see essentially the same logic in reapply_impl() below. // const prerequisite& p (i->prerequisite); // Ignore excluded. // include_type pi (include (a, t, p)); if (!pi) continue; // Ignore unresolved targets that are imported from other projects. // We are definitely not installing those. // if (p.proj) continue; // Let a customized rule have its say. // // Note: we assume that if the filter enters the group, then it // iterates over all its members. // if (!is) is = a.operation () != update_id ? install_scope (t) : nullptr; pair fr (filter (*is, a, t, i, me)); const target* pt (fr.first); uint64_t options (fr.second); lookup l; if (pt == nullptr) { l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); } // Check if this prerequisite is explicitly "not installable", that // is, there is the 'install' variable and its value is false. // // At first, this might seem redundand since we could have let the // file_rule below take care of it. The nuance is this: this // prerequsite can be in a different subproject that hasn't loaded the // install module (and therefore has no file_rule registered). The // typical example would be the 'tests' subproject. // // Note: not the same as lookup_install() above. // else if ((l = (*pt)[var_install (*p.scope.root_scope ())]) && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); pt = nullptr; } // If this is not a file-based target (e.g., a target group such as // libu{}) then ignore it if there is no rule to install. // else if (pt->is_a ()) { match_sync (a, *pt, options); } else if (!try_match_sync (a, *pt, options).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; } if (pt != nullptr || reapply) { // Use auxiliary data for a NULL entry to distinguish between // filtered out (1) and ignored for other reasons (0). // pts.push_back ( prerequisite_target (pt, pi, fr.first == nullptr ? 1 : 0)); } } return default_recipe; } void alias_rule:: reapply_impl (action a, target& t, match_extra& me) const { tracer trace ("install::alias_rule::reapply"); assert (a.operation () != update_id); optional is; // Iterate over prerequisites and prerequisite targets in parallel. // auto& pts (t.prerequisite_targets[a]); size_t j (0), n (pts.size ()), en (0); auto pms (group_prerequisite_members (a, t, members_mode::never)); for (auto i (pms.begin ()), e (pms.end ()); i != e && j != n; ++i, ++j, ++en) { // The same logic as in apply() above except that we skip // prerequisites that were not filtered out. // const prerequisite& p (i->prerequisite); include_type pi (include (a, t, p)); if (!pi) continue; if (p.proj) continue; prerequisite_target& pto (pts[j]); if (pto.target != nullptr || pto.data == 0) continue; if (!is) is = a.operation () != update_id ? install_scope (t) : nullptr; pair fr (filter (*is, a, t, i, me)); const target* pt (fr.first); uint64_t options (fr.second); lookup l; if (pt == nullptr) { l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); } else if ((l = (*pt)[var_install (*p.scope.root_scope ())]) && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); pt = nullptr; } else if (pt->is_a ()) { match_sync (a, *pt, options); } else if (!try_match_sync (a, *pt, options).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; } pto = prerequisite_target (pt, pi, fr.first == nullptr ? 1 : 0); } assert (en == n); // Did not call apply() with true for reapply? } // group_rule // const group_rule group_rule::instance (false /* see_through_only */); bool group_rule:: match (action a, target& t) const { return (!see_through_only || t.type ().see_through ()) && alias_rule::match (a, t); } bool group_rule:: filter (action, const target&, const target&) const { return true; } pair group_rule:: filter (const scope* is, action, const target& t, const prerequisite& p, match_extra&) const { const uint64_t options (match_extra::all_options); // No definition. pair r (nullptr, options); // The same logic as in file_rule::filter() below. // if (p.is_a ()) { const scope& rs (*p.scope.root_scope ()); if (p.vars.empty () || cast_empty (p.vars[var_install (rs)]).string () != "true") return r; } const target& pt (search (t, p)); if (is == nullptr || pt.in (*is)) r.first = &pt; return r; } recipe group_rule:: apply (action a, target& t, match_extra& me) const { tracer trace ("install::group_rule::apply"); // Resolve group members. // // Remember that we are called twice: first during update for install // (pre-operation) and then during install. During the former, we rely // 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. // // If the rule could not resolve the group, then we ignore it. // group_view gv (a.outer () ? resolve_members (a, t) : t.group_members (a)); 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) { const target* mt (gv.members[i]); if (mt == nullptr) continue; // Let a customized rule have its say. // if (!filter (a, t, *mt)) { l5 ([&]{trace << "ignoring " << *mt << " (filtered out)";}); continue; } // See if we were explicitly instructed not to touch this target // (the same semantics as in the prerequisites match). // // Note: not the same as lookup_install() above. // auto l ((*mt)[var_install (rs)]); if (l && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *mt << " (not installable)";}); continue; } match_sync (a, *mt); pts.push_back (mt); // Never ad hoc. } } // Delegate to the base rule. // return alias_rule::apply (a, t, me); } // file_rule // const file_rule file_rule::instance; bool file_rule:: match (action, target&) const { // We always match, even if this target is not installable (so that we // can ignore it; see apply()). // return true; } bool file_rule:: filter (action, const target&, const target&) const { return true; } pair file_rule:: filter (const scope* is, action a, const target& t, prerequisite_iterator& i, match_extra& me) const { assert (i->member == nullptr); return filter (is, a, t, i->prerequisite, me); } pair file_rule:: filter (const scope* is, action, const target& t, const prerequisite& p, match_extra&) const { const uint64_t options (match_extra::all_options); // No definition. pair r (nullptr, options); // See also group_rule::filter() with identical semantics. // if (p.is_a ()) { 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 (p.vars[var_install (rs)]).string () != "true") return r; } const target& pt (search (t, p)); if (is == nullptr || pt.in (*is)) r.first = &pt; return r; } recipe file_rule:: apply (action a, target& t, match_extra& me) const { recipe r (apply_impl (a, t, me)); return r != nullptr ? move (r) : noop_recipe; } recipe file_rule:: apply (action, target&) const { assert (false); // Never called. return nullptr; } recipe file_rule:: apply_impl (action a, target& t, match_extra& me, bool reapply) const { tracer trace ("install::file_rule::apply"); assert (!reapply || a.operation () != update_id); // Note that we are called both as the outer part during the update-for- // un/install pre-operation and as the inner part during the un/install // operation itself. // // In both cases we first determine if the target is installable and // return noop if it's not. Otherwise, in the first case (update-for- // un/install) we delegate to the normal update and in the second // (un/install) -- perform the install. // if (!lookup_install (t, "install")) return empty_recipe; // In both cases, the next step is to search, match, and collect all the // installable prerequisites. // // But first, in case of the update pre-operation, match the inner rule // (actual update). We used to do this after matching the prerequisites // but the inner rule may provide some rule-specific information (like // the target extension for exe{}) that may be required during the // prerequisite search (like the base name for in{}; this no longer // reproduces likely due to the changes to exe{} extension derivation // but a contrived arrangement can still be made to trigger this). // // But then we discovered that doing this before the prerequisites messes // up with the for-install signaling. Specifically, matching the // prerequisites may signal that they are being updated for install, // for example, for a library via a metadata library used in a moc // recipe. While matching the inner rule may trigger updating during // match of such prerequisites, for example, a source file generated by // that moc recipe that depends on this metadata library. If we match // prerequisites before, then the library that is pulled by the metadata // library will be updated before we had a chance to signal that it // should be updated for install. // // To try to accommodate both cases (as best as we can) we now split the // inner rule match into two steps: we do the match before and apply // after. This allows rules that deal with tricky prerequisites like // in{} to assign the target path in match() instead of apply() (see // in::rule, for example). // #if 0 optional unchanged; if (a.operation () == update_id) unchanged = match_inner (a, t, unmatch::unchanged).first; #else action ia (a.inner_action ()); if (a.operation () == update_id) match_only_sync (ia, t); #endif optional is; // Installation scope (resolve lazily). auto& pts (t.prerequisite_targets[a]); auto pms (group_prerequisite_members (a, t, members_mode::never)); for (auto i (pms.begin ()), e (pms.end ()); i != e; ++i) { // NOTE: see essentially the same logic in reapply_impl() below. // const prerequisite& p (i->prerequisite); // Ignore excluded. // include_type pi (include (a, t, p)); if (!pi) continue; // Ignore unresolved targets that are imported from other projects. // We are definitely not installing those. // if (p.proj) continue; // Let a customized rule have its say. // // Note: we assume that if the filter enters the group, then it // iterates over all its members. // if (!is) is = a.operation () != update_id ? install_scope (t) : nullptr; pair fr (filter (*is, a, t, i, me)); const target* pt (fr.first); uint64_t options (fr.second); lookup l; if (pt == nullptr) { l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); } // // See if we were explicitly instructed not to touch this target (the // same semantics as in alias_rule). // // Note: not the same as lookup_install() above. // else if ((l = (*pt)[var_install (*p.scope.root_scope ())]) && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); pt = nullptr; } else if (pt->is_a ()) { // If the matched rule returned noop_recipe, then the target state // is set to unchanged as an optimization. Use this knowledge to // optimize things on our side as well since this will help a lot // when updating static installable content (headers, documentation, // etc). // // Regarding options, the expectation here is that they are not used // for the update operation. And for install/uninstall, if they are // used, then they don't effect whether the target is unchanged. All // feels reasonable. // if (match_sync (a, *pt, unmatch::unchanged, options).first) pt = nullptr; } else if (!try_match_sync (a, *pt, options).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; } if (pt != nullptr || reapply) { // Use auxiliary data for a NULL entry to distinguish between // filtered out (1) and ignored for other reasons (0). // pts.push_back ( prerequisite_target (pt, pi, fr.first == nullptr ? 1 : 0)); } } #if 1 optional unchanged; if (a.operation () == update_id) unchanged = match_sync (ia, t, unmatch::unchanged).first; #endif if (a.operation () == update_id) { return *unchanged ? (pts.empty () ? noop_recipe : default_recipe) : &perform_update; } else { return [this] (action a, const target& t) { return a.operation () == install_id ? perform_install (a, t) : perform_uninstall (a, t); }; } } void file_rule:: reapply_impl (action a, target& t, match_extra& me) const { tracer trace ("install::file_rule::reapply"); assert (a.operation () != update_id); optional is; // Iterate over prerequisites and prerequisite targets in parallel. // auto& pts (t.prerequisite_targets[a]); size_t j (0), n (pts.size ()), en (0); auto pms (group_prerequisite_members (a, t, members_mode::never)); for (auto i (pms.begin ()), e (pms.end ()); i != e && j != n; ++i, ++j, ++en) { // The same logic as in apply() above except that we skip // prerequisites that were not filtered out. // const prerequisite& p (i->prerequisite); include_type pi (include (a, t, p)); if (!pi) continue; if (p.proj) continue; prerequisite_target& pto (pts[j]); if (pto.target != nullptr || pto.data == 0) continue; if (!is) is = a.operation () != update_id ? install_scope (t) : nullptr; pair fr (filter (*is, a, t, i, me)); const target* pt (fr.first); uint64_t options (fr.second); lookup l; if (pt == nullptr) { l5 ([&]{trace << "ignoring " << p << " (filtered out)";}); } else if ((l = (*pt)[var_install (*p.scope.root_scope ())]) && cast (l).string () == "false") { l5 ([&]{trace << "ignoring " << *pt << " (not installable)";}); pt = nullptr; } else if (pt->is_a ()) { if (match_sync (a, *pt, unmatch::unchanged, options).first) pt = nullptr; } else if (!try_match_sync (a, *pt, options).first) { l5 ([&]{trace << "ignoring " << *pt << " (no rule)";}); pt = nullptr; } pto = prerequisite_target (pt, pi, fr.first == nullptr ? 1 : 0); } assert (en == n); // Did not call apply() with true for reapply? } target_state file_rule:: perform_update (action a, const target& t) { // First execute the inner recipe then prerequisites. // target_state ts (execute_inner (a, t)); if (t.prerequisite_targets[a].size () != 0) ts |= straight_execute_prerequisites (a, t); return ts; } bool file_rule:: install_extra (const file&, const install_dir&) const { return false; } bool file_rule:: uninstall_extra (const file&, const install_dir&) const { return false; } auto_rmfile file_rule:: install_pre (const file& t, const install_dir&) const { return auto_rmfile (t.path (), false /* active */); } bool file_rule:: install_post (const file& t, const install_dir& id, auto_rmfile&&) const { return install_extra (t, id); } struct install_dir { dir_path dir; // If not NULL, then point to the corresponding install.* value. // const string* sudo = nullptr; const path* cmd = nullptr; const strings* options = nullptr; const string* mode = nullptr; const string* dir_mode = nullptr; explicit install_dir (dir_path d = dir_path ()): dir (move (d)) {} install_dir (dir_path d, const install_dir& b) : dir (move (d)), sudo (b.sudo), cmd (b.cmd), options (b.options), mode (b.mode), dir_mode (b.dir_mode) {} }; using install_dirs = vector; // Calculate a subdirectory based on l's location (*.subdirs) and if not // empty add it to install_dirs. Return the new last element. // static install_dir& resolve_subdir (install_dirs& rs, const target& t, const scope& s, const lookup& l) { // Find the scope from which this value came and use as a base // to calculate the subdirectory. // for (const scope* p (&s); p != nullptr; p = p->parent_scope ()) { if (l.belongs (*p, true)) // Include target type/pattern-specific. { // The target can be in out or src. // 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. Note: the // result is normalized. // if (!d.empty ()) rs.emplace_back (rs.back ().dir / d, rs.back ()); break; } } return rs.back (); } // 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. // // @@ TODO: detect cycles (maybe by keeping a stack-based linked list). // static install_dirs resolve (const scope& s, const target* t, dir_path d, bool fail_unknown = true, const string* var = nullptr) { install_dirs rs; if (d.absolute ()) rs.emplace_back (move (d.normalize ())); else { // If it is relative, then the first component is treated as the // installation directory name, e.g., bin, sbin, lib, etc. Look it // up and recurse. // if (d.empty ()) fail << "empty installation directory name"; const string& sn (*d.begin ()); const string var ("install." + sn); if (const dir_path* dn = lookup_install (s, var)) { if (dn->empty ()) fail << "empty installation directory for name " << sn << info << "did you specified empty config." << var << "?"; rs = resolve (s, t, *dn, fail_unknown, &var); if (rs.empty ()) { assert (!fail_unknown); return rs; // Empty. } d = rs.back ().dir / dir_path (++d.begin (), d.end ()); rs.emplace_back (move (d.normalize ()), rs.back ()); } else { if (fail_unknown) fail << "unknown installation directory name '" << sn << "'" << info << "did you forget to specify config." << var << "?" << info << "specify !config." << var << "=... if installing " << "from multiple projects"; return rs; // Empty. } } install_dir* r (&rs.back ()); // Override components in install_dir if we have our own. // if (var != nullptr) { if (auto l = s[*var + ".sudo"]) r->sudo = &cast (l); if (auto l = s[*var + ".cmd"]) r->cmd = &cast (l); if (auto l = s[*var + ".mode"]) r->mode = &cast (l); if (auto l = s[*var + ".dir_mode"]) r->dir_mode = &cast (l); if (auto l = s[*var + ".options"]) r->options = &cast (l); if (t != nullptr) { if (auto l = s[*var + ".subdirs"]) { if (cast (l)) r = &resolve_subdir (rs, *t, s, l); } } } // Set globals for unspecified components. // if (r->sudo == nullptr) r->sudo = cast_null (s["config.install.sudo"]); if (r->cmd == nullptr) r->cmd = &cast (s["config.install.cmd"]); if (r->options == nullptr) r->options = cast_null (s["config.install.options"]); if (r->mode == nullptr) r->mode = &cast (s["config.install.mode"]); if (r->dir_mode == nullptr) r->dir_mode = &cast (s["config.install.dir_mode"]); return rs; } static dir_path resolve_dir (const scope& s, const target* t, dir_path d, dir_path rb, bool 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, dir_path rb, bool fail_unknown) { return resolve_dir (t.base_scope (), &t, move (d), move (rb), fail_unknown); } dir_path resolve_dir (const scope& s, dir_path d, dir_path rb, bool fail_unknown) { 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 resolve_file (const file& f) { // Note: similar logic to perform_install(). // const path* p (lookup_install (f, "install")); if (p == nullptr) // Not installable. return path (); bool n (!p->to_directory ()); dir_path d (n ? p->directory () : path_cast (*p)); if (n && d.empty ()) fail << "relative installation file path '" << p << "' has no directory component"; install_dirs ids (resolve (f, d)); if (!n) { if (auto l = f["install.subdirs"]) { if (cast (l)) resolve_subdir (ids, f, f.base_scope (), l); } } return ids.back ().dir / (n ? p->leaf () : f.path ().leaf ()); } // On Windows we use MSYS2 install.exe and MSYS2 by default ignores // filesystem permissions (noacl mount option). And this means, for // example, that .exe that we install won't be runnable by Windows (MSYS2 // itself will still run them since it recognizes the file extension). // // NOTE: this is no longer the case and we now use noacl (and acl causes // other problems; see baseutils fstab for details). // // The way we work around this (at least in our distribution of the MSYS2 // tools) is by changing the mount option for cygdrives (/c, /d, etc) to // acl. But that's not all: we also have to install via a path that "hits" // one of those mount points, c:\foo won't work, we have to use /c/foo. // So this function translates an absolute Windows path to its MSYS // representation. // // Note that we return the result as a string, not dir_path since path // starting with / are illegal on Windows. Also note that the result // doesn't have the trailing slash. // static string msys_path (const dir_path& d) { assert (d.absolute ()); string s (d.representation ()); // First replace ':' with the drive letter (so the path is no longer // absolute) but postpone setting the first character to / until we are // a string. // s[1] = lcase (s[0]); s = dir_path (move (s)).posix_string (); s[0] = '/'; return s; } 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 // the same directory creation commands over and over again (because we // don't actually create them). There are two alternative ways to solve // this: actually create the directories or simply don't show anything. // While we use the former approach during update (see mkdir() in // filesystem), here it feels like we really shouldn't be touching the // destination filesystem. Plus, not showing anything will be symmetric // with uninstall since the directories won't be empty (because we don't // actually uninstall any files). // // 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)); try { if (dir_exists (chd)) // May throw (e.g., EACCES). return; } catch (const system_error& e) { fail << "invalid installation directory " << chd << ": " << e; } // While install -d will create all the intermediate components between // base and dir, we do it explicitly, one at a time. This way the output // is symmetrical to uninstall() below. // // Note that if the chroot directory does not exist, then install -d // will create it and we don't bother removing it. // if (d != base.dir) { dir_path pd (d.directory ()); if (pd != base.dir) install_d (rs, base, pd, t, verbosity); } cstrings args; string reld ( ctx.build_host->class_ == "windows" ? msys_path (chd) : relative (chd).string ()); if (base.sudo != nullptr) args.push_back (base.sudo->c_str ()); args.push_back (base.cmd->string ().c_str ()); args.push_back ("-d"); if (base.options != nullptr) append_options (args, *base.options); args.push_back ("-m"); args.push_back (base.dir_mode->c_str ()); args.push_back (reld.c_str ()); args.push_back (nullptr); process_path pp (run_search (args[0])); if (verb >= verbosity) { if (verb >= 2) print_process (args); else if (verb) print_diag ("install -d", chd); // See also `install -l` below. } 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:: install_f (const scope& rs, const install_dir& base, const path& name, const file& t, 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 ( ctx.build_host->class_ == "windows" ? msys_path (chd) : relative (chd).string ()); if (!name.empty ()) { reld += path::traits_type::directory_separator; reld += name.string (); } cstrings args; if (base.sudo != nullptr) args.push_back (base.sudo->c_str ()); args.push_back (base.cmd->string ().c_str ()); if (base.options != nullptr) append_options (args, *base.options); args.push_back ("-m"); args.push_back (base.mode->c_str ()); args.push_back (relf.string ().c_str ()); args.push_back (reld.c_str ()); args.push_back (nullptr); process_path pp (run_search (args[0])); if (verb >= verbosity) { if (verb >= 2) print_process (args); else if (verb) { if (name.empty ()) print_diag ("install", t, chd); else print_diag ("install", t, chd / name); } } if (!ctx.dry_run) 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& link, const file& target, const path& link_target, uint16_t verbosity) { assert (link.simple () && !link.empty ()); context& ctx (rs.ctx); if (!filter_entry (rs, base.dir, link, entry_type::symlink)) return; if (link_target.absolute () && cast_false (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, // won't work if we have sudo. Also, we would have to deal with existing // destinations (ln's -f takes care of that). So we are just going to // always (sudo or not) use ln unless we are on Windows, where we will // use mkanylink(). // #ifndef _WIN32 const char* args_a[] = { base.sudo != nullptr ? base.sudo->c_str () : nullptr, "ln", "-sf", link_target.string ().c_str (), rell.string ().c_str (), nullptr}; const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); process_path pp (run_search (args[0])); if (verb >= verbosity) { if (verb >= 2) print_process (args); else if (verb) { // 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 (ctx, pp, args, verb >= verbosity ? 1 : verb_never /* finish_verbosity */); #else // The -f part. // // We use uninstall_f() since reliably removing stuff on Windows is no // easy feat (see uninstall_f() for details). // uninstall_f (rs, base, nullptr /* target */, link, 3 /* verbosity */); if (verb >= verbosity) { if (verb >= 2) text << "ln -sf " << link_target.string () << ' ' << rell.string (); else if (verb) print_diag ("install -l", link_target, chd / link); } if (!ctx.dry_run) try { mkanylink (link_target, rell, true /* copy */); } catch (const pair& e) { const char* w (e.first == entry_type::regular ? "copy" : e.first == entry_type::symlink ? "symlink" : e.first == entry_type::other ? "hardlink" : nullptr); 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:: perform_install (action a, const target& xt) const { const file& t (xt.as ()); const path& tp (t.path ()); // Path should have been assigned by update unless it is unreal. // assert (!tp.empty () || t.mtime () == timestamp_unreal); const scope& rs (t.root_scope ()); auto install_target = [&rs, this] (const file& t, const path& p, uint16_t verbosity) { // Note: similar logic to resolve_file(). // bool n (!p.to_directory ()); dir_path d (n ? p.directory () : path_cast (p)); if (n && d.empty ()) fail << "relative installation file path '" << p << "' has no directory component"; // Resolve target directory. // install_dirs ids (resolve (t, d)); // Handle install.subdirs if one was specified. Unless the target path // includes the file name in which case we assume it's a "final" path. // if (!n) { if (auto l = t["install.subdirs"]) { if (cast (l)) resolve_subdir (ids, t, t.base_scope (), l); } } // Create leading directories. Note that we are using the leading // directory (if there is one) for the creation information (mode, // sudo, etc). // for (auto i (ids.begin ()), j (i); i != ids.end (); j = i++) install_d (rs, *j, i->dir, t, verbosity); // install -d install_dir& id (ids.back ()); // Override mode if one was specified. // if (auto l = t["install.mode"]) id.mode = &cast (l); // Install the target. // auto_rmfile f (install_pre (t, id)); // If install_pre() returned a different file name, make sure we // install it as the original. // const path& tp (t.path ()); const path& fp (f.path); install_f ( rs, id, n ? p.leaf () : fp.leaf () != tp.leaf () ? tp.leaf () : path (), t, f.path, verbosity); install_post (t, id, move (f)); }; // First handle installable prerequisites. // target_state r (straight_execute_prerequisites (a, t)); bool fr (filter (a, t, t)); // Then installable ad hoc group members, if any. // for (const target* m (t.adhoc_member); m != nullptr; m = m->adhoc_member) { if (const file* mf = m->is_a ()) { if (!mf->path ().empty () && mf->mtime () != timestamp_nonexistent) { if (filter (a, t, *mf)) { if (const path* p = lookup_install (*mf, "install")) { install_target (*mf, *p, !fr || tp.empty () ? 1 : 2); r |= target_state::changed; } } } } } // Finally install the target itself (since we got here we know the // install variable is there). // if (fr && !tp.empty ()) { install_target (t, cast (t[var_install (rs)]), 1); r |= target_state::changed; } return r; } bool file_rule:: uninstall_d (const scope& rs, const install_dir& base, const dir_path& d, uint16_t verbosity) { assert (d.absolute ()); context& ctx (rs.ctx); // See install_d() for the rationale. // if (ctx.dry_run || !filter_entry (rs, d, path (), entry_type::directory)) return false; dir_path chd (chroot_path (rs, d)); // Figure out if we should try to remove this directory. Note that if // it doesn't exist, then we may still need to remove outer ones. // bool r (false); try { if ((r = dir_exists (chd))) // May throw (e.g., EACCES). { if (!dir_empty (chd)) // May also throw. return false; // Won't be able to remove any outer directories. } } catch (const system_error& e) { fail << "invalid installation directory " << chd << ": " << e; } if (r) { dir_path reld (relative (chd)); // Normally when we need to remove a file or directory we do it // directly without calling rm/rmdir. This however, won't work if we // have sudo. So we are going to do it both ways. // // While there is no sudo on Windows, deleting things that are being // used can get complicated. So we will always use rm/rmdir from // MSYS2/Cygwin which go above and beyond to accomplish the mission. // // Note also that it's possible we didn't create the directory and // won't be able to remove it due to permissions (for example, on Mac // OS we cannot remove empty /usr/local even with sudo). So instead of // failing we issue a warning and skip the directory. // #ifndef _WIN32 if (base.sudo == nullptr) { if (verb >= verbosity) { if (verb >= 2) text << "rmdir " << reld; else if (verb) print_diag ("uninstall -d", chd); } try { try_rmdir (chd); } catch (const system_error&) { r = false; } } else #endif { const char* args_a[] = { base.sudo != nullptr ? base.sudo->c_str () : nullptr, "rmdir", reld.string ().c_str (), nullptr}; const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); process_path pp (run_search (args[0])); if (verb >= verbosity) { if (verb >= 2) print_process (args); else if (verb) print_diag ("uninstall -d", chd); } 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) { warn << "unable to remove empty directory " << chd << ", ignoring"; return false; } } // If we have more empty directories between base and dir, then try // to clean them up as well. // if (d != base.dir) { dir_path pd (d.directory ()); if (pd != base.dir) r = uninstall_d (rs, base, pd, verbosity) || r; } return r; } static void uninstall_f_impl (const scope& rs, const install_dir& base, const path& f, uint16_t verbosity) { context& ctx (rs.ctx); path relf (relative (f)); // The same story as with uninstall -d (on Windows rm is also from // MSYS2/Cygwin). // #ifndef _WIN32 if (base.sudo == nullptr) { if (verb >= verbosity && verb >= 2) text << "rm " << relf; if (!ctx.dry_run) { try { try_rmfile (f); } catch (const system_error& e) { fail << "unable to remove file " << f << ": " << e; } } } else #endif { const char* args_a[] = { base.sudo != nullptr ? base.sudo->c_str () : nullptr, "rm", "-f", relf.string ().c_str (), nullptr}; const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); process_path pp (run_search (args[0])); 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); 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; } target_state file_rule:: perform_uninstall (action a, const target& xt) const { const file& t (xt.as ()); const path& tp (t.path ()); // Path should have been assigned by update unless it is unreal. // assert (!tp.empty () || t.mtime () == timestamp_unreal); const scope& rs (t.root_scope ()); auto uninstall_target = [&rs, this] (const file& t, const path& p, uint16_t verbosity) -> target_state { bool n (!p.to_directory ()); dir_path d (n ? p.directory () : path_cast (p)); if (n && d.empty ()) fail << "relative installation file path '" << p << "' has no directory component"; // Resolve target directory. // install_dirs ids (resolve (t, d)); // Handle install.subdirs if one was specified. // if (!n) { if (auto l = t["install.subdirs"]) { if (cast (l)) resolve_subdir (ids, t, t.base_scope (), l); } } // Remove extras and the target itself. // const install_dir& id (ids.back ()); target_state r (uninstall_extra (t, id) ? target_state::changed : target_state::unchanged); if (uninstall_f (rs, id, &t, n ? p.leaf () : path (), verbosity)) r |= target_state::changed; // Clean up empty leading directories (in reverse). // // Note that we are using the leading directory (if there is one) for // the clean up information (sudo, etc). We may also try to uninstall // the same directory via different bases (e.g., root and exec_bin). // for (auto i (ids.rbegin ()), j (i), e (ids.rend ()); i != e; j = ++i) { if (uninstall_d (rs, ++j != e ? *j : *i, i->dir, verbosity)) r |= target_state::changed; } return r; }; // Reverse order of installation: first the target itself (since we got // here we know the install variable is there). // target_state r (target_state::unchanged); bool fr (filter (a, t, t)); if (fr && !tp.empty ()) r |= uninstall_target (t, cast (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 // single-linked list). // for (const target* m (t.adhoc_member); m != nullptr; m = m->adhoc_member) { if (const file* mf = m->is_a ()) { if (!mf->path ().empty () && mf->mtime () != timestamp_nonexistent) { if (filter (a, t, *mf)) { if (const path* p = lookup_install (*m, "install")) { r |= uninstall_target ( *mf, *p, !fr || tp.empty () || r != target_state::changed ? 1 : 2); } } } } } // Finally handle installable prerequisites. // r |= reverse_execute_prerequisites (a, t); return r; } // fsdir_rule // const fsdir_rule fsdir_rule::instance; bool fsdir_rule:: match (action, target&) const { // We always match. // // Note that we are called both as the outer part during the update-for- // un/install pre-operation and as the inner part during the un/install // operation itself. // return true; } recipe fsdir_rule:: apply (action a, target& t) const { // If this is outer part of the update-for-un/install, delegate to the // default fsdir rule. Otherwise, this is a noop (we don't install // fsdir{}). // // For now we also assume we don't need to do anything for prerequisites // (the only sensible prerequisite of fsdir{} is another fsdir{}). // if (a.operation () == update_id) { match_inner (a, t); return inner_recipe; } else return noop_recipe; } } }