From 4776ab7859e71bb6cec004a1aea05324ad33fd1d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 19 Aug 2016 15:35:06 +0200 Subject: Implement uninstall operation --- build2/bin/init.cxx | 1 + build2/c/init.cxx | 1 + build2/cc/common | 7 +- build2/cc/link.cxx | 14 +- build2/cc/module.cxx | 33 +-- build2/context.cxx | 1 + build2/cxx/init.cxx | 1 + build2/install/init.cxx | 12 +- build2/install/operation | 1 + build2/install/operation.cxx | 20 ++ build2/install/rule | 3 + build2/install/rule.cxx | 516 ++++++++++++++++++++++++++++++++----------- build2/operation | 24 +- 13 files changed, 473 insertions(+), 161 deletions(-) diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 1d87fd2..424a729 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -399,6 +399,7 @@ namespace build2 // modules before all others? // r.insert (perform_install_id, "bin.lib", lib_); + r.insert (perform_uninstall_id, "bin.lib", lib_); } return true; diff --git a/build2/c/init.cxx b/build2/c/init.cxx index 7795181..52bf0b6 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -218,6 +218,7 @@ namespace build2 "c.compile", "c.link", "c.install", + "c.uninstall", cast (r[cm.x_id]), cast (r[cm.x_target]), diff --git a/build2/cc/common b/build2/cc/common index 95f205a..2be290a 100644 --- a/build2/cc/common +++ b/build2/cc/common @@ -84,6 +84,7 @@ namespace build2 const char* x_compile; // Rule names. const char* x_link; const char* x_install; + const char* x_uninstall; // Cached values for some commonly-used variables. // @@ -122,6 +123,7 @@ namespace build2 const char* compile, const char* link, const char* install, + const char* uninstall, const string& id, const string& tg, const string& sys, @@ -130,7 +132,10 @@ namespace build2 const target_type* const* hdr, const target_type* const* inc) : config_data (cd), - x_compile (compile), x_link (link), x_install (install), + x_compile (compile), + x_link (link), + x_install (install), + x_uninstall (uninstall), cid (id), ctg (tg), tsys (sys), tclass (class_), x_src (src), x_hdr (hdr), x_inc (inc) {} }; diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index df1fa7e..61dd0ad 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -595,8 +595,12 @@ namespace build2 // "library meta-information protocol". Don't do this if we are // called from the install rule just to check if we would match. // + auto op (a.operation ()); + auto oop (a.outer_operation ()); + if (seen_lib && lt != otype::e && - a.operation () != install_id && a.outer_operation () != install_id) + op != install_id && oop != install_id && + op != uninstall_id && oop != uninstall_id) { if (t.group != nullptr) t.group->prerequisite_targets.clear (); // lib{}'s @@ -1087,6 +1091,8 @@ namespace build2 { tracer trace (x, "link::perform_update"); + auto oop (a.outer_operation ()); + file& t (static_cast (xt)); scope& rs (t.root_scope ()); @@ -1107,7 +1113,7 @@ namespace build2 // assembly itself is generated later, after updating the target. Omit // it if we are updating for install. // - if (a.outer_operation () != install_id) + if (oop != install_id && oop != uninstall_id) rpath_timestamp = windows_rpath_timestamp (t); path mf ( @@ -1354,7 +1360,7 @@ namespace build2 { if (libs* ls = pt->is_a ()) { - if (a.outer_operation () != install_id) + if (oop != install_id && oop != uninstall_id) { sargs.push_back ("-Wl,-rpath," + ls->path ().directory ().string ()); @@ -1781,7 +1787,7 @@ namespace build2 // if (lt == otype::e && tclass == "windows") { - if (a.outer_operation () != install_id) + if (oop != install_id && oop != uninstall_id) windows_rpath_assembly (t, cast (rs[x_target_cpu]), rpath_timestamp, diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index ec7178b..d3849e1 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -307,30 +307,32 @@ namespace build2 link& lr (*this); install& ir (*this); - r.insert (perform_update_id, x_compile, cr); - r.insert (perform_clean_id, x_compile, cr); - r.insert (configure_update_id, x_compile, cr); + r.insert (perform_update_id, x_compile, cr); + r.insert (perform_clean_id, x_compile, cr); + r.insert (configure_update_id, x_compile, cr); - r.insert (perform_update_id, x_link, lr); - r.insert (perform_clean_id, x_link, lr); - r.insert (configure_update_id, x_link, lr); + r.insert (perform_update_id, x_link, lr); + r.insert (perform_clean_id, x_link, lr); + r.insert (configure_update_id, x_link, lr); - r.insert (perform_install_id, x_install, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_uninstall, ir); // Only register static object/library rules if the bin.ar module is // loaded (by us or by the user). // if (cast_false (b["bin.ar.loaded"])) { - r.insert (perform_update_id, x_compile, cr); - r.insert (perform_clean_id, x_compile, cr); - r.insert (configure_update_id, x_compile, cr); + r.insert (perform_update_id, x_compile, cr); + r.insert (perform_clean_id, x_compile, cr); + r.insert (configure_update_id, x_compile, cr); - r.insert (perform_update_id, x_link, lr); - r.insert (perform_clean_id, x_link, lr); - r.insert (configure_update_id, x_link, lr); + r.insert (perform_update_id, x_link, lr); + r.insert (perform_clean_id, x_link, lr); + r.insert (configure_update_id, x_link, lr); - r.insert (perform_install_id, x_install, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_uninstall, ir); } r.insert (perform_update_id, x_compile, cr); @@ -341,7 +343,8 @@ namespace build2 r.insert (perform_clean_id, x_link, lr); r.insert (configure_update_id, x_link, lr); - r.insert (perform_install_id, x_install, ir); + r.insert (perform_install_id, x_install, ir); + r.insert (perform_uninstall_id, x_uninstall, ir); } } } diff --git a/build2/context.cxx b/build2/context.cxx index 670397f..422ecac 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -74,6 +74,7 @@ namespace build2 operation_table.insert ("clean"); operation_table.insert ("test"); operation_table.insert ("install"); + operation_table.insert ("uninstall"); // Create global scope. For Win32 this is not a "real" root path. // On POSIX, however, this is a real path. See the comment in diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index 7170282..14134f1 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -225,6 +225,7 @@ namespace build2 "cxx.compile", "cxx.link", "cxx.install", + "cxx.uninstall", cast (r[cm.x_id]), cast (r[cm.x_target]), diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 06a3366..221cfcf 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -130,9 +130,10 @@ namespace build2 l5 ([&]{trace << "for " << r.out_path ();}); - // Register the install operation. + // Register the install and uninstall operations. // r.operations.insert (install_id, install); + r.operations.insert (uninstall_id, uninstall); } static const path cmd ("install"); @@ -186,10 +187,13 @@ namespace build2 v.insert ("install.mode"); } - // Register our alias and file installer rule. + // Register our alias and file rules. // - b.rules.insert (perform_install_id, "install.alias", alias_); - b.rules.insert (perform_install_id, "install.file", file_); + b.rules.insert (perform_install_id, "install.alias", alias_); + b.rules.insert (perform_uninstall_id, "uninstall.alias", alias_); + + b.rules.insert (perform_install_id, "install.file", file_); + b.rules.insert (perform_uninstall_id, "uinstall.file", file_); // Configuration. // diff --git a/build2/install/operation b/build2/install/operation index 8c93627..9ae44cd 100644 --- a/build2/install/operation +++ b/build2/install/operation @@ -15,6 +15,7 @@ namespace build2 namespace install { extern operation_info install; + extern operation_info uninstall; } } diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx index 77deb2c..f08d85d 100644 --- a/build2/install/operation.cxx +++ b/build2/install/operation.cxx @@ -29,5 +29,25 @@ namespace build2 &install_pre, nullptr }; + + // Note that we run update as a pre-operation, just like install. Which + // may seem bizarre at first. We do it to obtain the exact same dependency + // graph as install so that we uninstall exactly the same set of files as + // install would install. Note that just matching the rules without + // executing them may not be enough: for example, a presence of an ad hoc + // group member may only be discovered after executing the rule (e.g., VC + // link.exe only creates a DLL's import library if there are any exported + // symbols). + // + operation_info uninstall { + uninstall_id, + "uninstall", + "uninstall", + "uninstalling", + "is not installed", + execution_mode::last, + &install_pre, + nullptr + }; } } diff --git a/build2/install/rule b/build2/install/rule index 9602190..99ebd8a 100644 --- a/build2/install/rule +++ b/build2/install/rule @@ -44,6 +44,9 @@ namespace build2 static target_state perform_install (action, target&); + + static target_state + perform_uninstall (action, target&); }; } } diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index ac2fc8f..7365cae 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -78,7 +78,6 @@ namespace build2 // file_rule // - match_result file_rule:: match (action a, target& t, const string&) const { @@ -119,11 +118,11 @@ namespace build2 // // 1. This target is installable. // 2. The action is either - // a. (perform, install, 0) or - // b. (*, update, install) + // a. (perform, [un]install, 0) or + // b. (*, update, [un]install) // - // In both cases, the next step is to search, match, and collect - // all the installable prerequisites. + // In both cases, the next step is to search, match, and collect all the + // installable prerequisites. // // @@ Perhaps if [noinstall] will be handled by the // group_prerequisite_members machinery, then we can just @@ -147,7 +146,7 @@ namespace build2 if (pt == nullptr) continue; - // See if were explicitly instructed not to install this target. + // See if we were explicitly instructed not to touch this target. // auto l ((*pt)["install"]); if (l && cast (l).string () == "false") @@ -218,22 +217,127 @@ namespace build2 }; } else - return &perform_install; + return a.operation () == uninstall_id + ? &perform_uninstall + : &perform_install; } struct install_dir { - // @@ Why do we copy these? Why not just point to the values in vars? - // - dir_path dir; - string sudo; - path cmd; - strings options; - string mode; - string dir_mode; + + // 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; + + // Resolve installation directory name to absolute directory path. Return + // all the super-directories leading up to the destination (last). + // + static install_dirs + resolve (target& t, dir_path d, 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. + // + const string& sn (*d.begin ()); + const string var ("install." + sn); + if (const dir_path* dn = lookup (t.base_scope (), var)) + { + rs = resolve (t, *dn, &var); + d = rs.back ().dir / dir_path (++d.begin (), d.end ()); + rs.emplace_back (move (d.normalize ()), rs.back ()); + } + else + fail << "unknown installation directory name " << sn << + info << "did you forget to specify config." << var << "?"; + } + + install_dir* r (&rs.back ()); + scope& s (t.base_scope ()); + + // 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 (auto l = s[*var + ".subdirs"]) + { + if (cast (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)) // Ok since no target/type in lookup. + { + // The target can be in out or src. + // + const dir_path& d ( + (t.out.empty () ? t.dir : t.out).leaf (p->out_path ())); + + // Add it as another leading directory rather than modifying + // the last one directly; somehow, it feels right. + // + rs.emplace_back (r->dir / d, rs.back ()); + r = &rs.back (); + break; + } + } + } + } + } + + // 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; + } + // 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 @@ -260,9 +364,34 @@ namespace build2 // install -d // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // static void - install (const install_dir& base, const dir_path& d) + install (const install_dir& base, const dir_path& d, bool verbose = true) { + try + { + if (dir_exists (d)) // May throw (e.g., EACCES). + return; + } + catch (const system_error& e) + { + fail << "invalid installation directory " << d << ": " << e.what (); + } + + // 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. + // + if (d != base.dir) + { + dir_path pd (d.directory ()); + + if (pd != base.dir) + install (base, pd, verbose); + } + cstrings args; dir_path reld ( @@ -270,23 +399,23 @@ namespace build2 ? msys_path (d) : relative (d)); - if (!base.sudo.empty ()) - args.push_back (base.sudo.c_str ()); + if (base.sudo != nullptr) + args.push_back (base.sudo->c_str ()); - args.push_back (base.cmd.string ().c_str ()); + args.push_back (base.cmd->string ().c_str ()); args.push_back ("-d"); - if (!base.options.empty ()) - append_options (args, base.options); + if (base.options != nullptr) + append_options (args, *base.options); args.push_back ("-m"); - args.push_back (base.dir_mode.c_str ()); + args.push_back (base.dir_mode->c_str ()); args.push_back (reld.string ().c_str ()); args.push_back (nullptr); if (verb >= 2) print_process (args); - else if (verb) + else if (verb && verbose) text << "install " << d; try @@ -324,16 +453,16 @@ namespace build2 cstrings args; - if (!base.sudo.empty ()) - args.push_back (base.sudo.c_str ()); + if (base.sudo != nullptr) + args.push_back (base.sudo->c_str ()); - args.push_back (base.cmd.string ().c_str ()); + args.push_back (base.cmd->string ().c_str ()); - if (!base.options.empty ()) - append_options (args, base.options); + if (base.options != nullptr) + append_options (args, *base.options); args.push_back ("-m"); - args.push_back (base.mode.c_str ()); + args.push_back (base.mode->c_str ()); args.push_back (relf.string ().c_str ()); args.push_back (reld.string ().c_str ()); args.push_back (nullptr); @@ -361,152 +490,287 @@ namespace build2 } } - // Resolve installation directory name to absolute directory path, - // creating leading directories as necessary. - // - static install_dir - resolve (target& t, dir_path d, const string* var = nullptr) + target_state file_rule:: + perform_install (action a, target& xt) { - install_dir r; + file& t (static_cast (xt)); + assert (!t.path ().empty ()); // Should have been assigned by update. + + auto install_target = [](file& t, const dir_path& d, bool verbose) + { + // Resolve target directory. + // + install_dirs ids (resolve (t, d)); + + // 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 (*j, i->dir, verbose); // install -d + + install_dir& id (ids.back ()); + + // Override mode if one was specified. + // + if (auto l = t["install.mode"]) + id.mode = &cast (l); + + install (id, t, verbose); + }; + + // First handle installable prerequisites. + // + target_state r (execute_prerequisites (a, t)); + + // Then installable ad hoc group members, if any. + // + for (target* m (t.member); m != nullptr; m = m->member) + { + if (const dir_path* d = lookup (*m, "install")) + install_target (static_cast (*m), *d, false); + } + + // Finally install the target itself (since we got here we know the + // install variable is there). + // + install_target (t, cast (t["install"]), true); + + return (r |= target_state::changed); + } + // uninstall -d + // + // We try remove all the directories between base and dir but not base + // itself unless base == dir. Return false if nothing has been removed + // (i.e., the directories do not exist or are not empty). + // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // + static bool + uninstall (const install_dir& base, const dir_path& d, bool verbose = true) + { + // 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 (d.absolute ()) - r.dir = move (d.normalize ()); - else + if ((r = dir_exists (d))) // May throw (e.g., EACCES). { - // 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. - // - const string& sn (*d.begin ()); - const string var ("install." + sn); - if (const dir_path* dn = lookup (t.base_scope (), var)) - { - r = resolve (t, *dn, &var); - d = r.dir / dir_path (++d.begin (), d.end ()); - r.dir = move (d.normalize ()); - - if (!dir_exists (r.dir)) // May throw (e.g., EACCES). - install (r, r.dir); // install -d - } - else - fail << "unknown installation directory name " << sn << - info << "did you forget to specify config." << var << "?"; + if (!dir_empty (d)) // May also throw. + return false; // Won't be able to remove any outer directories. } + } + catch (const system_error& e) + { + fail << "invalid installation directory " << d << ": " << e.what (); + } - scope& s (t.base_scope ()); + if (r) + { + dir_path reld (relative (d)); - // Override components in install_dir if we have our own. + // 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. Thankfully, no sudo + // on Windows. // - if (var != nullptr) + if (base.sudo == 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 (verb >= 2) + text << "rmdir " << reld; + else if (verb && verbose) + text << "uninstall " << reld; - if (auto l = s[*var + ".subdirs"]) + try { - if (cast (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)) // Ok since no target/type in lookup. - { - // The target can be in out or src. - // - r.dir /= (t.out.empty () ? t.dir : t.out).leaf ( - p->out_path ()); - break; - } - } - } + try_rmdir (d); + } + catch (const system_error& e) + { + fail << "unable to remove directory " << d << ": " << e.what (); } } + else + { + cstrings args {base.sudo->c_str (), + "rmdir", + reld.string ().c_str (), + nullptr}; - // Set globals for unspecified components. - // - if (r.sudo.empty ()) - if (auto l = s["config.install.sudo"]) - r.sudo = cast (l); + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "uninstall " << reld; - if (r.cmd.empty ()) - r.cmd = cast (s["config.install.cmd"]); + try + { + process pr (args.data ()); - if (r.options.empty ()) - if (auto l = s["config.install.options"]) - r.options = cast (l); + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); - if (r.mode.empty ()) - r.mode = cast (s["config.install.mode"]); + if (e.child ()) + exit (1); - if (r.dir_mode.empty ()) - r.dir_mode = cast (s["config.install.dir_mode"]); + throw failed (); + } + } + } - // If the directory still doesn't exist, then this means it was - // specified as absolute (it will normally be install.root with - // everything else defined in term of it). We used to fail in this - // case but that proved to be just too anal. So now we create it. - // - // Also, with the .subdirs logic we may end up with a non-existent - // subdirectory. - // - if (!dir_exists (r.dir)) // May throw (e.g., EACCES). - // fail << "installation directory " << d << " does not exist"; - install (r, r.dir); // install -d + // 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 (base, pd, verbose) || r; + } + + return r; + } + + // uninstall + // + // Return false if nothing has been removed (i.e., the file does not + // exist). + // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // + static bool + uninstall (const install_dir& base, file& t, bool verbose = true) + { + path f (base.dir / t.path ().leaf ()); + + try + { + if (!file_exists (f)) // May throw (e.g., EACCES). + return false; } catch (const system_error& e) { - fail << "invalid installation directory " << r.dir << ": " - << e.what (); + fail << "invalid installation path " << f << ": " << e.what (); } - return r; + path relf (relative (f)); + + // The same story as with uninstall -d. + // + if (base.sudo == nullptr) + { + if (verb >= 2) + text << "rm " << relf; + else if (verb && verbose) + text << "uninstall " << t; + + try + { + try_rmfile (f); + } + catch (const system_error& e) + { + fail << "unable to remove file " << f << ": " << e.what (); + } + } + else + { + cstrings args {base.sudo->c_str (), + "rm", + "-f", + relf.string ().c_str (), + nullptr}; + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "uninstall " << t; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e.what (); + + if (e.child ()) + exit (1); + + throw failed (); + } + } + + return true; } target_state file_rule:: - perform_install (action a, target& xt) + perform_uninstall (action a, target& xt) { file& t (static_cast (xt)); assert (!t.path ().empty ()); // Should have been assigned by update. - auto install_target = [](file& t, const dir_path& d, bool verbose) + auto uninstall_target = [](file& t, const dir_path& d, bool verbose) + -> target_state { - // Resolve and, if necessary, create target directory. + // Resolve target directory. // - install_dir id (resolve (t, d)); + install_dirs ids (resolve (t, d)); - // Override mode if one was specified. + // Remove the target itself. // - if (auto l = t["install.mode"]) - id.mode = cast (l); + target_state r (uninstall (ids.back (), t, verbose) + ? target_state::changed + : target_state::unchanged); - install (id, t, verbose); + // 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). + // + for (auto i (ids.rbegin ()), j (i), e (ids.rend ()); i != e; j = ++i) + { + if (uninstall (++j != e ? *j : *i, i->dir, verbose)) + r |= target_state::changed; + } + + return r; }; - // First handle installable prerequisites. + // Reverse order of installation: first the target itself (since we got + // here we know the install variable is there). // - target_state r (execute_prerequisites (a, t)); + target_state r ( + uninstall_target (t, cast (t["install"]), true)); - // Then installable ad hoc group members, if any. + // 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 (target* m (t.member); m != nullptr; m = m->member) { if (const dir_path* d = lookup (*m, "install")) - install_target (static_cast (*m), *d, false); + r |= uninstall_target (static_cast (*m), + *d, + r != target_state::changed); } - // Finally install the target itself (since we got here we know the - // install variable is there). + // Finally handle installable prerequisites. // - install_target (t, cast (t["install"]), true); + r |= reverse_execute_prerequisites (a, t); - return (r |= target_state::changed); + return r; } } } diff --git a/build2/operation b/build2/operation index 152353b..730a958 100644 --- a/build2/operation +++ b/build2/operation @@ -108,16 +108,18 @@ namespace build2 // that no operation was explicitly specified by the user. If adding // something here remember to update the man page. // - const operation_id default_id = 1; // Shall be first. - const operation_id update_id = 2; - const operation_id clean_id = 3; - const operation_id test_id = 4; - const operation_id install_id = 5; - - const action_id perform_update_id = (perform_id << 4) | update_id; - const action_id perform_clean_id = (perform_id << 4) | clean_id; - const action_id perform_test_id = (perform_id << 4) | test_id; - const action_id perform_install_id = (perform_id << 4) | install_id; + const operation_id default_id = 1; // Shall be first. + const operation_id update_id = 2; + const operation_id clean_id = 3; + const operation_id test_id = 4; + const operation_id install_id = 5; + const operation_id uninstall_id = 6; + + const action_id perform_update_id = (perform_id << 4) | update_id; + const action_id perform_clean_id = (perform_id << 4) | clean_id; + const action_id perform_test_id = (perform_id << 4) | test_id; + const action_id perform_install_id = (perform_id << 4) | install_id; + const action_id perform_uninstall_id = (perform_id << 4) | uninstall_id; const action_id configure_update_id = (configure_id << 4) | update_id; @@ -140,7 +142,7 @@ namespace build2 // If we use the last/back mode for, say, clean, then we will remove // targets in the order inverse to the way they were updated. While // this sounds like an elegant idea, are there any practical benefits - // of doing it this way. As it turns out there is (at least) one: when + // of doing it this way? As it turns out there is (at least) one: when // we are removing a directory (see fsdir{}), we want to do it after // all the targets that depend on it (such as files, sub-directories) // were removed. If we do it before, then the directory won't be empty -- cgit v1.1