From 6205a2d9eb7db0a25959ae34dc5406f228da92a5 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 8 Jul 2016 14:05:28 +0200 Subject: Implement limited rpath emulation for Windows --- build2/algorithm | 20 +- build2/algorithm.cxx | 67 +++++-- build2/config/operation.cxx | 4 +- build2/context | 68 ++++--- build2/context.cxx | 26 +-- build2/context.txx | 75 ++++--- build2/cxx/link | 3 + build2/cxx/link.cxx | 470 +++++++++++++++++++++++++++++++++++++++----- build2/types | 1 + 9 files changed, 589 insertions(+), 145 deletions(-) (limited to 'build2') diff --git a/build2/algorithm b/build2/algorithm index d7dddba..e57b94d 100644 --- a/build2/algorithm +++ b/build2/algorithm @@ -222,13 +222,19 @@ namespace build2 perform_clean_depdb (action, target&); // Helper for custom perform(clean) implementations that cleans extra files - // specified as a list of extensions. The extension string can be NULL, in - // which case it is ignored. Otherwise, the first character can be '+', in - // which case the extension is added (without the plus) to the existing - // extension (if any). In all other cases, the old extension is replaced - // with the new one. For example: - // - // clean_extra (a, t, {"+.d", ".lib"}); + // 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. The next character can be '+', in which case the + // extension is added (without the plus) to the existing extension (if + // any). In all other cases, the old extension is replaced with the new one + // (so if you want to strip the extension, specify ""). For example: + // + // 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. // target_state clean_extra (action, file&, initializer_list extra_ext); diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 02f3b8e..5aca7ee 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -481,28 +481,62 @@ namespace build2 clean_extra (action a, file& ft, initializer_list es) { // Clean the extras first and don't print the commands at verbosity level - // below 3. + // below 3. Note the first extra file/directory that actually got removed + // for diagnostics below. // target_state er (target_state::unchanged); - path ef; // First extra file that actually got removed (see below). + bool ed (false); + path ep; for (const char* e: es) { if (e == nullptr) continue; - path f; + bool d (*e == '/'); + if (d) + ++e; + + path p; if (*e == '+') - f = ft.path () + ++e; + p = ft.path () + ++e; else - f = ft.path ().base () + e; + p = ft.path ().base () + e; - target_state r (rmfile (f, false) - ? target_state::changed - : target_state::unchanged); + target_state r (target_state::unchanged); - if (r == target_state::changed && ef.empty ()) - ef = move (f); + if (d) + { + dir_path dp (path_cast (p)); + + switch (build2::rmdir_r (dp, true, 3)) + { + 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; + } + } + else + { + if (rmfile (p, 3)) + r = target_state::changed; + } + + if (r == target_state::changed && ep.empty ()) + { + ed = d; + ep = move (p); + } er |= r; } @@ -518,12 +552,12 @@ namespace build2 const path& f (fm->path ()); - target_state r (rmfile (f, false) + target_state r (rmfile (f, 3) ? target_state::changed : target_state::unchanged); - if (r == target_state::changed && ef.empty ()) - ef = f; + if (r == target_state::changed && ep.empty ()) + ep = f; er |= r; } @@ -556,7 +590,12 @@ namespace build2 if (tr != target_state::changed && er == target_state::changed) { if (verb > 0 && verb < 3) - text << "rm " << ef; + { + if (ed) + text << "rm -r " << path_cast (ep); + else + text << "rm " << ep; + } } tr |= er; diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 20425ea..1a207ca 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -44,7 +44,7 @@ namespace build2 path f (out_root / src_root_file); if (verb) - text << (verb >= 2 ? "config::save " : "save ") << f; + text << (verb >= 2 ? "cat >" : "save ") << f; try { @@ -73,7 +73,7 @@ namespace build2 path f (out_root / config_file); if (verb) - text << (verb >= 2 ? "config::save " : "save ") << f; + text << (verb >= 2 ? "cat >" : "save ") << f; const module& mod (*root.modules.lookup (module::name)); diff --git a/build2/context b/build2/context index 30d29d1..3f47af6 100644 --- a/build2/context +++ b/build2/context @@ -5,6 +5,8 @@ #ifndef BUILD2_CONTEXT #define BUILD2_CONTEXT +#include // enable_if + #include #include @@ -73,45 +75,59 @@ namespace build2 explicit operator bool () const {return v == T::success;} }; - // Create the directory and print the standard diagnostics. Note that - // this implementation is not suitable if it is expected that the - // directory will exist in the majority of case and performance is + // Create the directory and print the standard diagnostics starting from + // the specified verbosity level. + // + // Note that this implementation is not suitable if it is expected that the + // directory will exist in the majority of cases and performance is // important. See the fsdir{} rule for details. // - fs_status - mkdir (const dir_path&); + using mkdir_status = butl::mkdir_status; - fs_status - mkdir_p (const dir_path&); + fs_status + mkdir (const dir_path&, uint16_t verbosity = 1); - // Remove the file and print the standard diagnostics. The second argument - // is only used in diagnostics, to print the target name. Passing the path - // for target will result in the relative path being printed. - // - // If verbose is false, then only print the command at verbosity level 3 - // or higher. + fs_status + mkdir_p (const dir_path&, uint16_t verbosity = 1); + + // Remove the file and print the standard diagnostics starting from the + // specified verbosity level. The second argument is only used in + // diagnostics, to print the target name. Passing the path for target will + // result in the relative path being printed. // + using rmfile_status = butl::rmfile_status; + template - fs_status - rmfile (const path&, const T& target, bool verbose = true); + fs_status + rmfile (const path&, const T& target, uint16_t verbosity = 1); - inline fs_status - rmfile (const path& f, bool verbose = true) {return rmfile (f, f, verbose);} + inline fs_status + rmfile (const path& f, int verbosity = 1) // Literal overload (int). + { + return rmfile (f, f, static_cast (verbosity)); + } - // Similar to rmfile() but for directories. + // Similar to rmfile() but for directories (note: not -r). // + using rmdir_status = butl::rmdir_status; + template - fs_status - rmdir (const dir_path&, const T& target); + fs_status + rmdir (const dir_path&, const T& target, uint16_t verbosity = 1); - inline fs_status - rmdir (const dir_path& d) {return rmdir (d, d);} + inline fs_status + rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int). + { + return rmdir (d, d, static_cast (verbosity)); + } - // Note that this function returns not_empty if we try to remove - // a working directory. + // Remove the directory recursively and print the standard diagnostics + // starting from the specified verbosity level. Note that this function + // returns not_empty if we try to remove a working directory. If the dir + // argument is false, then the directory itself is not removed. // - fs_status - rmdir_r (const dir_path&); + fs_status + rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1); // Return the src/out directory corresponding to the given out/src. The // passed directory should be a sub-directory of out/src_root. diff --git a/build2/context.cxx b/build2/context.cxx index 59eb912..5530ce3 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -326,10 +326,10 @@ namespace build2 } fs_status - mkdir (const dir_path& d) + mkdir (const dir_path& d, uint16_t v) { - // We don't want to print the command if the directory already - // exists. This makes the below code a bit ugly. + // We don't want to print the command if the directory already exists. + // This makes the below code a bit ugly. // mkdir_status ms; @@ -339,7 +339,7 @@ namespace build2 } catch (const system_error& e) { - if (verb) + if (verb >= v) text << "mkdir " << d; error << "unable to create directory " << d << ": " << e.what (); @@ -348,7 +348,7 @@ namespace build2 if (ms == mkdir_status::success) { - if (verb) + if (verb >= v) text << "mkdir " << d; } @@ -356,10 +356,10 @@ namespace build2 } fs_status - mkdir_p (const dir_path& d) + mkdir_p (const dir_path& d, uint16_t v) { - // We don't want to print the command if the directory already - // exists. This makes the below code a bit ugly. + // We don't want to print the command if the directory already exists. + // This makes the below code a bit ugly. // mkdir_status ms; @@ -369,7 +369,7 @@ namespace build2 } catch (const system_error& e) { - if (verb) + if (verb >= v) text << "mkdir -p " << d; error << "unable to create directory " << d << ": " << e.what (); @@ -378,7 +378,7 @@ namespace build2 if (ms == mkdir_status::success) { - if (verb) + if (verb >= v) text << "mkdir -p " << d; } @@ -386,7 +386,7 @@ namespace build2 } fs_status - rmdir_r (const dir_path& d) + rmdir_r (const dir_path& d, bool dir, uint16_t v) { using namespace butl; @@ -396,12 +396,12 @@ namespace build2 if (!dir_exists (d)) return rmdir_status::not_exist; - if (verb) + if (verb >= v) text << "rmdir -r " << d; try { - butl::rmdir_r (d); + butl::rmdir_r (d, dir); } catch (const system_error& e) { diff --git a/build2/context.txx b/build2/context.txx index 1f3fce9..9223681 100644 --- a/build2/context.txx +++ b/build2/context.txx @@ -8,20 +8,25 @@ namespace build2 { template fs_status - rmfile (const path& f, const T& t, bool verbose) + rmfile (const path& f, const T& t, uint16_t v) { using namespace butl; - // Verbosity thresholds. + // We don't want to print the command if we couldn't remove the file + // because it does not exist (just like we don't print the update command + // if the file is up to date). This makes the below code a bit ugly. // - uint16_t l1 (verbose ? 2 : 3); - uint16_t l2 (verbose ? 1 : 3); + auto print = [&f, &t, v] () + { + if (verb >= v) + { + if (verb >= 2) + text << "rm " << f; + else if (verb) + text << "rm " << t; + } + }; - // We don't want to print the command if we couldn't remove the - // file because it does not exist (just like we don't print the - // update command if the file is up to date). This makes the - // below code a bit ugly. - // rmfile_status rs; try @@ -30,51 +35,48 @@ namespace build2 } catch (const system_error& e) { - if (verb >= l1) - text << "rm " << f; - else if (verb >= l2) - text << "rm " << t; - + print (); error << "unable to remove file " << f << ": " << e.what (); throw failed (); } if (rs == rmfile_status::success) - { - if (verb >= l1) - text << "rm " << f; - else if (verb >= l2) - text << "rm " << t; - } + print (); return rs; } template fs_status - rmdir (const dir_path& d, const T& t) + rmdir (const dir_path& d, const T& t, uint16_t v) { using namespace butl; bool w (work.sub (d)); // Don't try to remove working directory. rmdir_status rs; - // We don't want to print the command if we couldn't remove the - // directory because it does not exist (just like we don't print - // mkdir if it already exists) or if it is not empty. This makes - // the below code a bit ugly. + // We don't want to print the command if we couldn't remove the directory + // because it does not exist (just like we don't print mkdir if it already + // exists) or if it is not empty. This makes the below code a bit ugly. // + auto print = [&d, &t, v] () + { + if (verb >= v) + { + if (verb >= 2) + text << "rm " << d; + else if (verb) + text << "rm " << t; + } + }; + try { rs = !w ? try_rmdir (d) : rmdir_status::not_empty; } catch (const system_error& e) { - if (verb >= 2) - text << "rmdir " << d; - else if (verb) - text << "rmdir " << t; - + print (); error << "unable to remove directory " << d << ": " << e.what (); throw failed (); } @@ -83,20 +85,17 @@ namespace build2 { case rmdir_status::success: { - if (verb >= 2) - text << "rmdir " << d; - else if (verb) - text << "rmdir " << t; - + print (); break; } case rmdir_status::not_empty: { - if (verb >= 2) - text << "directory " << d << " is " + if (verb >= v && verb >= 2) + { + text << d << " is " << (w ? "current working directory" : "not empty") << ", not removing"; - + } break; } case rmdir_status::not_exist: diff --git a/build2/cxx/link b/build2/cxx/link index d0584de..ca45e17 100644 --- a/build2/cxx/link +++ b/build2/cxx/link @@ -28,6 +28,9 @@ namespace build2 static target_state perform_update (action, target&); + static target_state + perform_clean (action, target&); + static link instance; public: diff --git a/build2/cxx/link.cxx b/build2/cxx/link.cxx index 0e4fd5a..4de3194 100644 --- a/build2/cxx/link.cxx +++ b/build2/cxx/link.cxx @@ -4,6 +4,9 @@ #include +#include // E* + +#include #include // exit() #include @@ -898,7 +901,7 @@ namespace build2 switch (a) { case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean_depdb; + case perform_clean_id: return &perform_clean; default: return noop_recipe; // Configure update. } } @@ -949,19 +952,339 @@ namespace build2 } } + // Provide limited emulation of the rpath functionality on Windows using a + // manifest and a side-by-side assembly. In a nutshell, the idea is to + // create an assembly with links to all the prerequisite DLLs. + // + // The scratch argument should be true if the DLL set has changed and we + // need to regenerate everything from scratch. Otherwise, we try to avoid + // unnecessary work by comparing the DLL timestamps against the assembly + // manifest file. + // + // If the manifest argument is false, then don't generate the target + // manifest (i.e., it will be embedded). + // + // Note that currently our assemblies contain all the DLLs that the + // executable depends on, recursively. The alternative approach could be + // to also create assemblies for DLLs. This appears to be possible (but we + // will have to use the resource ID 2 for such a manifest). And it will + // probably be necessary for DLLs that are loaded dynamically with + // LoadLibrary(). The tricky part is how such nested assemblies will be + // found. Since we are effectively (from the loader's point of view) + // copying the DLLs, we will also have to copy their assemblies (because + // the loader looks for them in the same directory as the DLL). It's not + // clear how well such nested assemblies are supported. + // + static timestamp + timestamp_dlls (target&); + + static void + collect_dlls (set&, target&); + + static void + emulate_rpath_windows (file& t, bool scratch, bool manifest) + { + // Assembly paths and name. + // + dir_path ad (path_cast (t.path () + ".dlls")); + string an (ad.leaf ().string ()); + path am (ad / path (an + ".manifest")); + + // First check if we actually need to do anything. Since most of the + // time we won't, we don't want to combine it with the collect_dlls() + // call below which allocates memory, etc. + // + if (!scratch) + { + // The corner case here is when timestamp_dlls() returns nonexistent + // signalling that there aren't any DLLs but the assembly manifest + // file exists. This, however, can only happen if we somehow managed + // to transition from the "have DLLs" state to "no DLLs" without going + // through the from scratch update. And this shouldn't happen (famous + // last words before a core dump). + // + if (timestamp_dlls (t) <= file_mtime (am)) + return; + } + + scope& rs (t.root_scope ()); + + // Next collect the set of DLLs that will be in our assembly. We need to + // do this recursively which means we may end up with duplicates. Also, + // it is possible that there will (no longer) be any DLLs which means we + // just need to clean things up. + // + set dlls; + collect_dlls (dlls, t); + bool empty (dlls.empty ()); + + // Target manifest. + // + path tm; + if (manifest) + tm = t.path () + ".manifest"; + + // Clean the assembly directory and make sure it exists. Maybe it would + // have been faster to overwrite the existing manifest rather than + // removing the old one and creating a new one. But this is definitely + // simpler. + // + { + rmdir_status s (build2::rmdir_r (ad, empty, 3)); + + // What if there is a user-defined manifest in the src directory? We + // would just overwrite it if src == out. While we could add a comment + // with some signature that can be used to detect an auto-generated + // manifest, we can also use the presence of the assembly directory as + // such a marker. + // + // @@ And what can we do instead? One idea is for the user to call it + // something else and we merge the two. Perhaps the link rule could + // have support for manifests (i.e., manifest will be one of the + // prerequisites). A similar problem is with embedded vs standalone + // manifests (embedded preferred starting from Vista). I guess if we + // support embedding manifests, then we can also merge them. + // + if (manifest && + s == rmdir_status::not_exist && + rs.src_path () == rs.out_path () && + file_exists (tm)) + { + fail << tm << " looks like a custom manifest" << + info << "remove it manually if that's not the case"; + } + + if (empty) + { + if (manifest) + rmfile (tm, 3); + + return; + } + + if (s == rmdir_status::not_exist) + mkdir (ad, 3); + } + + // Translate the compiler target CPU value to the processorArchitecture + // attribute value. + // + const string& tcpu (cast (rs["cxx.target.cpu"])); + + const char* pa (tcpu == "i386" || tcpu == "i686" ? "x86" : + tcpu == "x86_64" ? "amd64" : + nullptr); + + if (pa == nullptr) + fail << "unable to translate CPU " << tcpu << " to manifest " + << "processor architecture"; + + if (verb >= 3) + text << "cat >" << am; + + try + { + ofstream ofs; + ofs.exceptions (ofstream::failbit | ofstream::badbit); + ofs.open (am.string ()); + + ofs << "\n" + << "\n" + << " \n"; + + scope& as (*rs.weak_scope ()); // Amalgamation scope. + + for (file* dt: dlls) + { + const path& dp (dt->path ()); // DLL path. + const path dn (dp.leaf ()); // DLL name. + const path lp (ad / dn); // Link path. + + auto print = [&dp, &lp] (const char* cmd) + { + if (verb >= 3) + text << cmd << ' ' << dp << ' ' << lp; + }; + + // First we try to create a symlink. If that fails (e.g., "Windows + // happens"), then we resort to hard links. If that doesn't work + // out either (e.g., not on the same filesystem), then we fall back + // to copies. So things are going to get a bit nested. + // + try + { + // For the symlink use a relative target path if both paths are + // part of the same amalgamation. This way if the amalgamation is + // moved as a whole, the links will remain valid. + // + if (dp.sub (as.out_path ())) + mksymlink (dp.relative (ad), lp); + else + mksymlink (dp, lp); + + print ("ln -s"); + } + catch (const system_error& e) + { + int c (e.code ().value ()); + + if (c != EPERM && c != ENOSYS) + { + print ("ln -s"); + fail << "unable to create symlink " << lp << ": " << e.what (); + } + + try + { + mkhardlink (dp, lp); + print ("ln"); + } + catch (const system_error& e) + { + int c (e.code ().value ()); + + if (c != EPERM && c != ENOSYS) + { + print ("ln"); + fail << "unable to create hard link " << lp << ": " + << e.what (); + } + + try + { + cpfile (dp, lp); + print ("cp"); + } + catch (const system_error& e) + { + print ("cp"); + fail << "unable to create copy " << lp << ": " << e.what (); + } + } + } + + ofs << " \n"; + } + + ofs << "\n"; + } + catch (const ofstream::failure&) + { + fail << "unable to write to " << am; + } + + // Create the manifest if requested. + // + if (!manifest) + return; + + if (verb >= 3) + text << "cat >" << tm; + + try + { + ofstream ofs; + ofs.exceptions (ofstream::failbit | ofstream::badbit); + ofs.open (tm.string (), ofstream::out | ofstream::trunc); + + ofs << "\n" + << "\n" + << "\n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << "\n"; + } + catch (const ofstream::failure&) + { + fail << "unable to write to " << tm; + } + } + + // Return the greatest (newest) timestamp of all the DLLs that we will be + // adding to the assembly or timestamp_nonexistent if there aren't any. + // + static timestamp + timestamp_dlls (target& t) + { + timestamp r (timestamp_nonexistent); + + for (target* pt: t.prerequisite_targets) + { + if (libso* ls = pt->is_a ()) + { + // This can be an installed library in which case we will have just + // the import stub but may also have just the DLL. For now we don't + // bother with installed libraries. + // + if (ls->member == nullptr) + continue; + + file& dll (static_cast (*ls->member)); + + // What if the DLL is in the same directory as the executable, will + // it still be found even if there is an assembly? On the other + // hand, handling it as any other won't hurt us much. + // + timestamp t; + + if ((t = dll.mtime ()) > r) + r = t; + + if ((t = timestamp_dlls (*ls)) > r) + r = t; + } + } + + return r; + } + + static void + collect_dlls (set& s, target& t) + { + for (target* pt: t.prerequisite_targets) + { + if (libso* ls = pt->is_a ()) + { + if (ls->member == nullptr) + continue; + + file& dll (static_cast (*ls->member)); + + s.insert (&dll); + collect_dlls (s, *ls); + } + } + } + target_state link:: perform_update (action a, target& xt) { tracer trace ("cxx::link::perform_update"); - path_target& t (static_cast (xt)); + file& t (static_cast (xt)); type lt (link_type (t)); bool so (lt == type::so); // Update prerequisites. // - bool up (execute_prerequisites (a, t, t.mtime ())); + bool update (execute_prerequisites (a, t, t.mtime ())); scope& rs (t.root_scope ()); @@ -1059,51 +1382,57 @@ namespace build2 append_options (args, t, "cxx.coptions"); append_std (args, rs, cid, t, std); - // Set soname. + // Handle soname/rpath. Emulation for Windows is done after we have + // built the target. // - if (so && tclass != "windows") + if (tclass == "windows") { - const string& leaf (t.path ().leaf ().string ()); + auto l (t["bin.rpath"]); - if (tclass == "macosx") + if (l && !l->empty ()) + fail << cast (rs["cxx.target"]) << " does not have rpath"; + } + else + { + // Set soname. + // + if (so) { - // With Mac OS 10.5 (Leopard) Apple finally caved in and gave us - // a way to emulate vanilla -rpath. - // - // It may seem natural to do something different on update for - // install. However, if we don't make it @rpath, then the user - // won't be able to use config.bin.rpath for installed libraries. - // - soname1 = "-install_name"; - soname2 = "@rpath/" + leaf; - } - else - soname1 = "-Wl,-soname," + leaf; + const string& leaf (t.path ().leaf ().string ()); - if (!soname1.empty ()) - args.push_back (soname1.c_str ()); + if (tclass == "macosx") + { + // With Mac OS 10.5 (Leopard) Apple finally caved in and gave us + // a way to emulate vanilla -rpath. + // + // It may seem natural to do something different on update for + // install. However, if we don't make it @rpath, then the user + // won't be able to use config.bin.rpath for installed libraries. + // + soname1 = "-install_name"; + soname2 = "@rpath/" + leaf; + } + else + soname1 = "-Wl,-soname," + leaf; - if (!soname2.empty ()) - args.push_back (soname2.c_str ()); - } + if (!soname1.empty ()) + args.push_back (soname1.c_str ()); - // Add rpaths. We used to first add the ones specified by the user so - // that they take precedence. But that caused problems if we have old - // versions of the libraries sitting in the rpath location (e.g., - // installed libraries). And if you think about this, it's probably - // correct to prefer libraries that we explicitly imported to the - // ones found via rpath. - // - // Note also that if this is update for install, then we don't add - // rpath of the imported libraries (i.e., we assume the are also - // installed). - // - if (tclass == "windows") - { - // @@ VC TODO: emulate own rpath somehow and complain on user's. - } - else - { + if (!soname2.empty ()) + args.push_back (soname2.c_str ()); + } + + // Add rpaths. We used to first add the ones specified by the user + // so that they take precedence. But that caused problems if we have + // old versions of the libraries sitting in the rpath location + // (e.g., installed libraries). And if you think about this, it's + // probably correct to prefer libraries that we explicitly imported + // to the ones found via rpath. + // + // Note also that if this is update for install, then we don't add + // rpath of the imported libraries (i.e., we assume they are also + // installed). + // for (target* pt: t.prerequisite_targets) { if (libso* ls = pt->is_a ()) @@ -1188,17 +1517,19 @@ namespace build2 } // If any of the above checks resulted in a mismatch (different linker, - // options, or input file set), or if the database is newer than the - // target (interrupted update) then force the target update. + // options or input file set), or if the database is newer than the + // target (interrupted update) then force the target update. Also + // note this situation in the "from scratch" flag. // + bool scratch (false); if (dd.writing () || dd.mtime () > t.mtime ()) - up = true; + scratch = update = true; dd.close (); // If nothing changed, then we are done. // - if (!up) + if (!update) return target_state::unchanged; // Ok, so we are updating. Finish building the command line. @@ -1387,6 +1718,11 @@ namespace build2 throw failed (); } + // 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 ()); + if (ranlib) { const char* args[] = { @@ -1415,6 +1751,13 @@ namespace build2 } } + // Emulate rpath on Windows. + // + if (lt == type::e && tclass == "windows") + emulate_rpath_windows (t, scratch, cid != "msvc"); + + rm.cancel (); + // Should we go to the filesystem and get the new mtime? We know the // file has been modified, so instead just use the current clock time. // It has the advantage of having the subseconds precision. @@ -1423,6 +1766,43 @@ namespace build2 return target_state::changed; } + target_state link:: + perform_clean (action a, target& xt) + { + tracer trace ("cxx::link::perform_clean"); + + file& t (static_cast (xt)); + + type lt (link_type (t)); + + scope& rs (t.root_scope ()); + const string& cid (cast (rs["cxx.id"])); + const string& tclass (cast (rs["cxx.target.class"])); + + if (lt == type::e && tclass == "windows") + { + bool m (cid != "msvc"); + + // Check for custom manifest, just like in emulate_rpath_windows(). + // + if (m && + rs.src_path () == rs.out_path () && + file_exists (t.path () + ".manifest") && + !dir_exists (path_cast (t.path () + ".dlls"))) + { + fail << t.path () + ".manifest" << " looks like a custom manifest" << + info << "remove it manually if that's not the case"; + } + + return clean_extra ( + a, + t, + {"+.d", (m ? "+.manifest" : nullptr), "/+.dlls"}); + } + else + return clean_extra (a, t, {"+.d"}); + } + link link::instance; } } diff --git a/build2/types b/build2/types index 943c9e1..6fdea4e 100644 --- a/build2/types +++ b/build2/types @@ -80,6 +80,7 @@ namespace build2 using butl::dir_path; using butl::basic_path; using butl::invalid_path; + using butl::path_cast; // Absolute directory path. Note that for now we don't do any checking that // the path is in fact absolute. -- cgit v1.1