From 2720b45ef0ca9fd58c11fd9b4f000e1cf3a0819d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 29 Aug 2016 19:31:16 +0200 Subject: Implement initial support for library versioning Currently we only support platform-independent versions that get appended to the library name. The magic incantation is this: lib{foo}: bin.lib.version = @-1.2 This will produce libfoo-1.2.so, libfoo-1.2.dll, etc. In the future we will support things like this: lib{foo}: bin.lib.version = linux@1.2.3 freebsd@1.2 windows@1.2 --- build2/algorithm | 28 ++-- build2/algorithm.cxx | 102 ++++++++------ build2/bin/init.cxx | 6 + build2/cc/install | 10 +- build2/cc/install.cxx | 54 ++++++++ build2/cc/link | 33 +++++ build2/cc/link.cxx | 342 +++++++++++++++++++++++++++++++++------------- build2/dist/operation.cxx | 2 +- build2/install/rule | 51 ++++++- build2/install/rule.cxx | 143 +++++++++++++------ build2/target.cxx | 17 ++- build2/utility.cxx | 2 +- 12 files changed, 587 insertions(+), 203 deletions(-) diff --git a/build2/algorithm b/build2/algorithm index 651ef24..9f43475 100644 --- a/build2/algorithm +++ b/build2/algorithm @@ -233,22 +233,32 @@ namespace build2 perform_clean_depdb (action, target&); // Helper for custom perform(clean) implementations that cleans extra files - // and directories (recursively) specified as a list of extensions. The - // extension string can be NULL, in which case it is ignored. If the first - // character is '/', then the resulting path is treated as a directory - // rather than a file. Next can come zero or more '-' characters which - // indicate the number of extensions that should stripped before the new - // extension (if any) is added (so if you want to strip the extension, - // specify "-"). For example: + // and directories (recursively) specified as a list of either absolute + // paths or "path derivation directives". The directive string can be NULL, + // or empty in which case it is ignored. If the last character in a + // directive is '/', then the resulting path is treated as a directory + // rather than a file. The directive can start with zero or more '-' + // characters which indicate the number of extensions that should be + // stripped before the new extension (if any) is added (so if you want to + // strip the extension, specify just "-"). For example: // - // clean_extra (a, t, {".d", "/.dlls", "-.dll"}); + // clean_extra (a, t, {".d", ".dlls/", "-.dll"}); // // The extra files/directories are removed first in the specified order // followed by the ad hoc group member, then target itself, and, finally, // the prerequisites in the reverse order. // + // You can also clean extra files derived from adhoc group members. + // target_state - clean_extra (action, file&, initializer_list extra_ext); + clean_extra (action, file&, + initializer_list> extra); + + inline target_state + clean_extra (action a, file& f, initializer_list extra) + { + return clean_extra (a, f, {extra}); + } } #include diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index cae6645..6b28232 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -532,7 +532,9 @@ namespace build2 } target_state - clean_extra (action a, file& ft, initializer_list es) + clean_extra (action a, + file& ft, + initializer_list> extra) { // Clean the extras first and don't print the commands at verbosity level // below 3. Note the first extra file/directory that actually got removed @@ -542,58 +544,77 @@ namespace build2 bool ed (false); path ep; - for (const char* e: es) + auto clean = [&er, &ed, &ep] (file& f, initializer_list es) { - if (e == nullptr) - continue; + for (const char* e: es) + { + size_t n; + if (e == nullptr || (n = strlen (e)) == 0) + continue; - bool d (*e == '/'); - if (d) - ++e; + path p; + bool d; - path p (ft.path ()); - for (; *e == '-'; ++e) - p = p.base (); + if (path::traits::absolute (e)) + { + p = path (e); + d = p.to_directory (); + } + else + { + if ((d = (e[n - 1] == '/'))) + --n; - p += e; + p = f.path (); + for (; *e == '-'; ++e) + p = p.base (); - target_state r (target_state::unchanged); + p.append (e, n); + } - if (d) - { - dir_path dp (path_cast (p)); + target_state r (target_state::unchanged); - switch (build2::rmdir_r (dp, true, 3)) + if (d) { - case rmdir_status::success: - { - r = target_state::changed; - break; - } - case rmdir_status::not_empty: + dir_path dp (path_cast (p)); + + switch (build2::rmdir_r (dp, true, 3)) { - if (verb >= 3) - text << dp << " is current working directory, not removing"; + case rmdir_status::success: + { + r = target_state::changed; + break; + } + case rmdir_status::not_empty: + { + if (verb >= 3) + text << dp << " is current working directory, not removing"; + break; + } + case rmdir_status::not_exist: break; } - case rmdir_status::not_exist: - break; } - } - else - { - if (rmfile (p, 3)) - r = target_state::changed; - } + else + { + if (rmfile (p, 3)) + r = target_state::changed; + } - if (r == target_state::changed && ep.empty ()) - { - ed = d; - ep = move (p); + if (r == target_state::changed && ep.empty ()) + { + ed = d; + ep = move (p); + } + + er |= r; } + }; - er |= r; - } + auto ei (extra.begin ()), ee (extra.end ()); + + if (ei != ee) + clean (ft, *ei++); // Now clean the ad hoc group file members, if any. // @@ -604,6 +625,9 @@ namespace build2 if (fm == nullptr || fm->path ().empty ()) continue; + if (ei != ee) + clean (*fm, *ei++); + const path& f (fm->path ()); target_state r (rmfile (f, 3) @@ -659,7 +683,7 @@ namespace build2 target_state perform_clean (action a, target& t) { - return clean_extra (a, dynamic_cast (t), {}); + return clean_extra (a, dynamic_cast (t), {nullptr}); } target_state diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index bf7dd69..6a417e7 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -4,6 +4,8 @@ #include +#include + #include #include @@ -79,6 +81,10 @@ namespace build2 v.insert ("bin.lib.suffix"); v.insert ("bin.exe.prefix"); v.insert ("bin.exe.suffix"); + + v.insert> ("bin.lib.version", + false, + variable_visibility::project); } // Configure. diff --git a/build2/cc/install b/build2/cc/install index e2be905..e8035b7 100644 --- a/build2/cc/install +++ b/build2/cc/install @@ -25,10 +25,16 @@ namespace build2 install (data&&, const link&); virtual target* - filter (action, target&, prerequisite_member) const; + filter (action, target&, prerequisite_member) const override; virtual match_result - match (action, target&, const string&) const; + match (action, target&, const string&) const override; + + virtual void + install_extra (file&, const install_dir&) const override; + + virtual bool + uninstall_extra (file&, const install_dir&) const override; private: const link& link_; diff --git a/build2/cc/install.cxx b/build2/cc/install.cxx index c62ea95..a69cc52 100644 --- a/build2/cc/install.cxx +++ b/build2/cc/install.cxx @@ -68,5 +68,59 @@ namespace build2 match_result r (link_.match (a, t, hint)); return r ? install::file_rule::match (a, t, "") : r; } + + void install:: + install_extra (file& t, const install_dir& id) const + { + if (t.is_a () && tclass != "windows") + { + // Here we may have a bunch of symlinks that we need to install. + // + link::libs_paths lp (link_.derive_libs_paths (t)); + + auto ln = [&id, this] (const path& f, const path& l) + { + install_l (id, f.leaf (), l.leaf (), false); + }; + + const path& lk (lp.link); + const path& so (lp.soname); + const path& in (lp.interm); + + const path* f (lp.real); + + if (!in.empty ()) {ln (*f, in); f = ∈} + if (!so.empty ()) {ln (*f, so); f = &so;} + if (!lk.empty ()) {ln (*f, lk);} + } + } + + bool install:: + uninstall_extra (file& t, const install_dir& id) const + { + bool r (false); + + if (t.is_a () && tclass != "windows") + { + // Here we may have a bunch of symlinks that we need to uninstall. + // + link::libs_paths lp (link_.derive_libs_paths (t)); + + auto rm = [&id, this] (const path& l) + { + return uninstall (id, nullptr, l.leaf (), false); + }; + + const path& lk (lp.link); + const path& so (lp.soname); + const path& in (lp.interm); + + if (!lk.empty ()) r = rm (lk) || r; + if (!so.empty ()) r = rm (so) || r; + if (!in.empty ()) r = rm (in) || r; + } + + return r; + } } } diff --git a/build2/cc/link b/build2/cc/link index 52ad4a4..cd8c10e 100644 --- a/build2/cc/link +++ b/build2/cc/link @@ -37,6 +37,39 @@ namespace build2 perform_clean (action, target&) const; private: + friend class install; + + // Shared library paths. + // + struct libs_paths + { + // If any (except real) is empty, then it is the same as the next + // one. Except for intermediate, for which empty indicates that it is + // not used. + // + // The libs{} path is always the real path. On Windows the link path + // is the import library. + // + // @@ TODO: change real to reference, make other const once cache the + // object. + // + path link; // What we link: libfoo.so + path soname; // SONAME: libfoo-1.so, libfoo.so.1 + path interm; // Intermediate: libfoo.so.1.2 + const path* real; // Real: libfoo.so.1.2.3 + + inline const path& + effect_link () const {return link.empty () ? effect_soname () : link;} + + inline const path& + effect_soname () const {return soname.empty () ? *real : soname;} + }; + + libs_paths + derive_libs_paths (file&) const; + + // Library handling. + // void append_libraries (strings&, file&, bool, scope&, lorder) const; diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index d0e887e..c363a10 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -4,6 +4,7 @@ #include +#include #include // exit() #include // cerr @@ -169,6 +170,145 @@ namespace build2 return &t; } + auto link:: + derive_libs_paths (file& ls) const -> libs_paths + { + const char* ext (nullptr); + const char* pfx (nullptr); + const char* sfx (nullptr); + + bool win (tclass == "windows"); + + if (win) + { + if (tsys == "mingw32") + pfx = "lib"; + + ext = "dll"; + } + else if (tclass == "macosx") + { + pfx = "lib"; + ext = "dylib"; + } + else + { + pfx = "lib"; + ext = "so"; + } + + if (auto l = ls["bin.lib.prefix"]) pfx = cast (l).c_str (); + if (auto l = ls["bin.lib.suffix"]) sfx = cast (l).c_str (); + + // First sort out which extension we are using. + // + const string& e (ls.derive_extension (ext)); + + auto append_ext = [&e] (path& p) + { + if (!e.empty ()) + { + p += '.'; + p += e; + } + }; + + // Figure out the version. + // + string v; + using verion_map = map; + if (const verion_map* m = cast_null (ls["bin.lib.version"])) + { + // First look for the target system. + // + auto i (m->find (tsys)); + + // Then look for the target class. + // + if (i == m->end ()) + i = m->find (tclass); + + // Then look for the wildcard. Since it is higly unlikely one can have + // a version that will work across platforms, this is only useful to + // say "all others -- no version". + // + if (i == m->end ()) + i = m->find ("*"); + + // At this stage the only platform-specific version we support is the + // "no version" override. + // + if (i != m->end () && !i->second.empty ()) + fail << i->first << "-specific bin.lib.version not yet supported"; + + // Finally look for the platform-independent version. + // + if (i == m->end ()) + i = m->find (""); + + // If we didn't find anything, fail. If the bin.lib.version was + // specified, then it should explicitly handle all the targets. + // + if (i == m->end ()) + fail << "no version for " << ctg << " in bin.lib.version" << + info << "considere adding " << tsys << "@ or " << tclass + << "@"; + + v = i->second; + } + + // Now determine the paths. + // + path lk, so, in; + const path* re (nullptr); + + // We start with the basic path. + // + path b (ls.dir); + { + if (pfx == nullptr) + b /= ls.name; + else + { + b /= pfx; + b += ls.name; + } + + if (sfx != nullptr) + b += sfx; + } + + // On Windows the real path is to libs{} and the link path is to the + // import library. + // + if (win) + { + // Usually on Windows the import library is called the same as the DLL + // but with the .lib extension. Which means it clashes with the static + // library. Instead of decorating the static library name with ugly + // suffixes (as is customary), let's use the MinGW approach (one must + // admit it's quite elegant) and call it .dll.lib. + // + lk = b; + append_ext (lk); + + libi& li (static_cast (*ls.member)); + lk = li.derive_path (move (lk), tsys == "mingw32" ? "a" : "lib"); + } + else if (!v.empty ()) + { + lk = b; + append_ext (lk); + } + + if (!v.empty ()) + b += v; + + re = &ls.derive_path (move (b)); + + return libs_paths {move (lk), move (so), move (in), re}; + } + recipe link:: apply (action a, target& xt, const match_result&) const { @@ -182,13 +322,26 @@ namespace build2 otype lt (link_type (t)); lorder lo (link_order (bs, lt)); - // Derive file name from target name. + // Derive file name(s) and add ad hoc group members. // - if (t.path ().empty ()) + auto add_adhoc = [a, &bs] (target& t, const char* type) -> file& + { + const target_type& tt (*bs.find_target_type (type)); + + if (t.member != nullptr) // Might already be there. + assert (t.member->type () == tt); + else + t.member = &search (tt, t.dir, t.out, t.name, nullptr, nullptr); + + file& r (static_cast (*t.member)); + r.recipe (a, group_recipe); + return r; + }; + { + const char* e (nullptr); // Extension. const char* p (nullptr); // Prefix. const char* s (nullptr); // Suffix. - const char* e (nullptr); // Extension. switch (lt) { @@ -202,18 +355,13 @@ namespace build2 if (auto l = t["bin.exe.prefix"]) p = cast (l).c_str (); if (auto l = t["bin.exe.suffix"]) s = cast (l).c_str (); + t.derive_path (e, p, s); break; } case otype::a: { - // To be anally precise, let's use the ar id to decide how to name - // the library in case, for example, someone wants to archive - // VC-compiled object files with MinGW ar or vice versa. - // - if (cast (rs["bin.ar.id"]) == "msvc") - { + if (cid == "msvc") e = "lib"; - } else { p = "lib"; @@ -223,94 +371,38 @@ namespace build2 if (auto l = t["bin.lib.prefix"]) p = cast (l).c_str (); if (auto l = t["bin.lib.suffix"]) s = cast (l).c_str (); + t.derive_path (e, p, s); break; } case otype::s: { - if (tclass == "macosx") - { - p = "lib"; - e = "dylib"; - } - else if (tclass == "windows") - { - // On Windows libs{} is an ad hoc group. The libs{} itself is - // the DLL and we add libi{} import library as its member (see - // below). - // - if (tsys == "mingw32") - p = "lib"; - - e = "dll"; - } - else - { - p = "lib"; - e = "so"; - } - - if (auto l = t["bin.lib.prefix"]) p = cast (l).c_str (); - if (auto l = t["bin.lib.suffix"]) s = cast (l).c_str (); + // On Windows libs{} is an ad hoc group. The libs{} itself is the + // DLL and we add libi{} import library as its member. + // + if (tclass == "windows") + add_adhoc (t, "libi"); + derive_libs_paths (t); break; } } - - t.derive_path (e, p, s); } - // Add ad hoc group members. + // PDB // - auto add_adhoc = [a, &bs] (target& t, const char* type) -> file& - { - const target_type& tt (*bs.find_target_type (type)); - - if (t.member != nullptr) // Might already be there. - assert (t.member->type () == tt); - else - t.member = &search (tt, t.dir, t.out, t.name, nullptr, nullptr); - - file& r (static_cast (*t.member)); - r.recipe (a, group_recipe); - return r; - }; - - if (tclass == "windows") + if (lt != otype::a && + cid == "msvc" && + (find_option ("/DEBUG", t, c_loptions, true) || + find_option ("/DEBUG", t, x_loptions, true))) { - // Import library. + // Add after the import library if any. // - if (lt == otype::s) - { - file& imp (add_adhoc (t, "libi")); - - // Usually on Windows the import library is called the same as the - // DLL but with the .lib extension. Which means it clashes with the - // static library. Instead of decorating the static library name - // with ugly suffixes (as is customary), let's use the MinGW - // approach (one must admit it's quite elegant) and call it - // .dll.lib. - // - if (imp.path ().empty ()) - imp.derive_path (t.path (), tsys == "mingw32" ? "a" : "lib"); - } + file& pdb (add_adhoc (t.member == nullptr ? t : *t.member, "pdb")); - // PDB + // We call it foo.{exe,dll}.pdb rather than just foo.pdb because we + // can have both foo.exe and foo.dll in the same directory. // - if (lt != otype::a && - cid == "msvc" && - (find_option ("/DEBUG", t, c_loptions, true) || - find_option ("/DEBUG", t, x_loptions, true))) - { - // Add after the import library if any. - // - file& pdb (add_adhoc (t.member == nullptr ? t : *t.member, "pdb")); - - // We call it foo.{exe,dll}.pdb rather than just foo.pdb because we - // can have both foo.exe and foo.dll in the same directory. - // - if (pdb.path ().empty ()) - pdb.derive_path (t.path (), "pdb"); - } + pdb.derive_path (t.path (), "pdb"); } t.prerequisite_targets.clear (); // See lib pre-match in match() above. @@ -964,6 +1056,10 @@ namespace build2 // cstrings args {nullptr}; // Reserve one for config.bin.ar/config.x. + libs_paths paths; + if (lt == otype::s) + paths = derive_libs_paths (t); + // Storage. // string soname1, soname2; @@ -1016,7 +1112,7 @@ namespace build2 // if (lt == otype::s) { - const string& leaf (t.path ().leaf ().string ()); + const string& leaf (paths.effect_soname ().leaf ().string ()); if (tclass == "macosx") { @@ -1413,7 +1509,7 @@ namespace build2 // Remove the target file if any of the subsequent actions fail. If we // don't do that, we will end up with a broken build that is up-to-date. // - auto_rmfile rm (t.path ()); + auto_rmfile rm (relt); if (ranlib) { @@ -1445,17 +1541,52 @@ namespace build2 } } - // For Windows generate rpath-emulating assembly (unless updaing for - // install). - // - if (lt == otype::e && tclass == "windows") + if (tclass == "windows") { - if (!for_install) + // For Windows generate rpath-emulating assembly (unless updaing for + // install). + // + if (lt == otype::e && !for_install) windows_rpath_assembly (t, bs, lo, cast (rs[x_target_cpu]), rpath_timestamp, scratch); } + else if (lt == otype::s) + { + // For shared libraries we may need to create a bunch of symlinks. + // + auto ln = [] (const path& f, const path& l) + { + // Note that we don't bother making the paths relative since they + // will only be seen at verbosity level 3. + // + if (verb >= 3) + text << "ln -sf " << f << ' ' << l; + + try + { + if (file_exists (l, false)) // The -f part. + try_rmfile (l); + + mksymlink (f, l); + } + catch (const system_error& e) + { + fail << "unable to create symlink " << l << ": " << e.what (); + } + }; + + const path& lk (paths.link); + const path& so (paths.soname); + const path& in (paths.interm); + + const path* f (paths.real); + + if (!in.empty ()) {ln (f->leaf (), in); f = ∈} + if (!so.empty ()) {ln (f->leaf (), so); f = &so;} + if (!lk.empty ()) {ln (f->leaf (), lk);} + } rm.cancel (); @@ -1472,13 +1603,14 @@ namespace build2 { file& t (static_cast (xt)); - initializer_list e; + libs_paths paths; + initializer_list> e; switch (link_type (t)) { case otype::a: { - e = {".d"}; + e = {{".d"}}; break; } case otype::e: @@ -1487,31 +1619,45 @@ namespace build2 { if (tsys == "mingw32") { - e = {".d", "/.dlls", ".manifest.o", ".manifest"}; + e = {{".d", ".dlls/", ".manifest.o", ".manifest"}}; } else { // Assuming it's VC or alike. Clean up .ilk in case the user // enabled incremental linking (note that .ilk replaces .exe). // - e = {".d", "/.dlls", ".manifest", "-.ilk"}; + e = {{".d", ".dlls/", ".manifest", "-.ilk"}}; } } else - e = {".d"}; + e = {{".d"}}; break; } case otype::s: { - if (tclass == "windows" && tsys != "mingw32") + if (tclass == "windows") { // Assuming it's VC or alike. Clean up .exp and .ilk. // - e = {".d", ".exp", "-.ilk"}; + // Note that .exp is based on the .lib, not .dll name. And with + // versioning their bases may not be the same. + // + if (tsys != "mingw32") + e = {{".d", "-.ilk"}, {"-.exp"}}; } else - e = {".d"}; + { + // Here we can have a bunch of symlinks that we need to remove. If + // the paths are empty, then they will be ignored. + // + paths = derive_libs_paths (t); + + e = {{".d", + paths.link.string ().c_str (), + paths.soname.string ().c_str (), + paths.interm.string ().c_str ()}}; + } break; } diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index bbd7357..d03fe28 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -397,7 +397,7 @@ namespace build2 // Delete old archive for good measure. // path ap (root / path (a)); - if (file_exists (ap)) + if (file_exists (ap, false)) rmfile (ap); // Use zip for .zip archives. Everything else goes to tar in the diff --git a/build2/install/rule b/build2/install/rule index 99ebd8a..56cd8f9 100644 --- a/build2/install/rule +++ b/build2/install/rule @@ -26,6 +26,8 @@ namespace build2 apply (action, target&, const match_result&) const; }; + struct install_dir; + class file_rule: public rule { public: @@ -42,11 +44,52 @@ namespace build2 virtual recipe apply (action, target&, const match_result&) const; - static target_state - perform_install (action, target&); + // Extra installation hooks. + // + using install_dir = install::install_dir; + + virtual void + install_extra (file&, const install_dir&) const; + + // Return true if anything was uninstalled. + // + virtual bool + uninstall_extra (file&, const install_dir&) const; + + // Installation "commands". + // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // + public: + // Install a symlink: base/link -> target. + // + static void + install_l (const install_dir& base, + const path& target, + const path& link, + bool verbose); + + // Uninstall a file or symlink: + // + // uninstall / rm /.leaf (); name empty + // uninstall rm /; target can be NULL + // + // Return false if nothing has been removed (i.e., the file does not + // exist). + // + static bool + uninstall (const install_dir& base, + file* t, + const path& name, + bool verbose); + + private: + target_state + perform_install (action, target&) const; - static target_state - perform_uninstall (action, target&); + target_state + perform_uninstall (action, target&) const; }; } } diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 257f14b..6753a23 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -218,12 +218,18 @@ namespace build2 return r; }; } + else if (a.operation () == install_id) + return [this] (action a, target& t) {return perform_install (a, t);}; else - return a.operation () == uninstall_id - ? &perform_uninstall - : &perform_install; + return [this] (action a, target& t) {return perform_uninstall (a, t);}; } + void file_rule:: + install_extra (file&, const install_dir&) const {} + + bool file_rule:: + uninstall_extra (file&, const install_dir&) const {return false;} + struct install_dir { dir_path dir; @@ -499,13 +505,60 @@ namespace build2 } } + void file_rule:: + install_l (const install_dir& base, + const path& target, + const path& link, + bool verbose) + { + path rell (relative (base.dir)); + 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 use ln. + // + const char* args_a[] = { + base.sudo != nullptr ? base.sudo->c_str () : nullptr, + "ln", + "-sf", + target.string ().c_str (), + rell.string ().c_str (), + nullptr}; + + const char** args (&args_a[base.sudo == nullptr ? 1 : 0]); + + if (verb >= 2) + print_process (args); + else if (verb && verbose) + text << "install " << rell << " -> " << target; + + try + { + process pr (args); + + 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 (); + } + } + target_state file_rule:: - perform_install (action a, target& xt) + perform_install (action a, target& xt) const { file& t (static_cast (xt)); assert (!t.path ().empty ()); // Should have been assigned by update. - auto install_target = [](file& t, const path& p, bool verbose) + auto install_target = [this] (file& t, const path& p, bool verbose) { bool n (!p.to_directory ()); dir_path d (n ? p.directory () : path_cast (p)); @@ -528,7 +581,10 @@ namespace build2 if (auto l = t["install.mode"]) id.mode = &cast (l); + // Install the target and extras. + // install (id, n ? p.leaf () : path (), t, verbose); + install_extra (t, id); }; // First handle installable prerequisites. @@ -607,10 +663,10 @@ namespace build2 } else { - cstrings args {base.sudo->c_str (), - "rmdir", - reld.string ().c_str (), - nullptr}; + const char* args[] = {base.sudo->c_str (), + "rmdir", + reld.string ().c_str (), + nullptr}; if (verb >= 2) print_process (args); @@ -619,7 +675,7 @@ namespace build2 try { - process pr (args.data ()); + process pr (args); if (!pr.wait ()) throw failed (); @@ -650,26 +706,21 @@ namespace build2 return r; } - // uninstall / - // 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 + bool file_rule:: uninstall (const install_dir& base, + file* t, const path& name, - file& t, - bool verbose = true) + bool verbose) { - path f (base.dir / (name.empty () ? t.path ().leaf () : name)); + assert (t != nullptr || !name.empty ()); + path f (base.dir / (name.empty () ? t->path ().leaf () : name)); try { - if (!file_exists (f)) // May throw (e.g., EACCES). + // 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) @@ -679,14 +730,20 @@ namespace build2 path relf (relative (f)); + if (verb == 1 && verbose) + { + if (t != nullptr) + text << "uninstall " << t; + else + text << "uninstall " << relf; + } + // 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 { @@ -699,20 +756,18 @@ namespace build2 } else { - cstrings args {base.sudo->c_str (), - "rm", - "-f", - relf.string ().c_str (), - nullptr}; + const char* 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 ()); + process pr (args); if (!pr.wait ()) throw failed (); @@ -732,12 +787,12 @@ namespace build2 } target_state file_rule:: - perform_uninstall (action a, target& xt) + perform_uninstall (action a, target& xt) const { file& t (static_cast (xt)); assert (!t.path ().empty ()); // Should have been assigned by update. - auto uninstall_target = [](file& t, const path& p, bool verbose) + auto uninstall_target = [this] (file& t, const path& p, bool verbose) -> target_state { bool n (!p.to_directory ()); @@ -747,12 +802,16 @@ namespace build2 // install_dirs ids (resolve (t, d)); - // Remove the target itself. + // Remove extras and the target itself. // - target_state r ( - uninstall (ids.back (), n ? p.leaf () : path (), t, verbose) - ? target_state::changed - : target_state::unchanged); + const install_dir& id (ids.back ()); + + target_state r (uninstall_extra (t, id) + ? target_state::changed + : target_state::unchanged); + + if (uninstall (id, &t, n ? p.leaf () : path (), verbose)) + r |= target_state::changed; // Clean up empty leading directories (in reverse). // @@ -761,7 +820,7 @@ namespace build2 // for (auto i (ids.rbegin ()), j (i), e (ids.rend ()); i != e; j = ++i) { - if (uninstall (++j != e ? *j : *i, i->dir, verbose)) + if (install::uninstall (++j != e ? *j : *i, i->dir, verbose)) r |= target_state::changed; } diff --git a/build2/target.cxx b/build2/target.cxx index 7a33dbc..8541deb 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -351,17 +351,20 @@ namespace build2 const path& path_target:: derive_path (const char* de, const char* np, const char* ns) { - string n; + path_type p (dir); - if (np != nullptr) - n += np; - - n += name; + if (np == nullptr) + p /= name; + else + { + p /= np; + p += name; + } if (ns != nullptr) - n += ns; + p += ns; - return derive_path (dir / path_type (move (n)), de); + return derive_path (move (p), de); } const path& path_target:: diff --git a/build2/utility.cxx b/build2/utility.cxx index db0d6b2..bb26b9d 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -105,7 +105,7 @@ namespace build2 } catch (const process_error& e) { - error << "unable execute " << f << ": " << e.what (); + error << "unable to execute " << f << ": " << e.what (); throw failed (); } -- cgit v1.1