diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-08-09 11:31:53 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-08-12 17:04:22 +0200 |
commit | 9fa5f73d00905568e8979d0c93ec4a8f645c81d5 (patch) | |
tree | f2bf937fa256c0ef2c9bbe05d3655d1985719405 /build2/cc/windows-rpath.cxx | |
parent | a1b2319ff2ddc8a6f139ee364cabe236ca62e23e (diff) |
Implement support for C compilation
We now have two new modules: cc (c-common) and c.
Diffstat (limited to 'build2/cc/windows-rpath.cxx')
-rw-r--r-- | build2/cc/windows-rpath.cxx | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx new file mode 100644 index 0000000..ea20a5c --- /dev/null +++ b/build2/cc/windows-rpath.cxx @@ -0,0 +1,273 @@ +// file : build2/cc/windows-rpath.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <errno.h> // E* + +#include <set> + +#include <build2/scope> +#include <build2/context> +#include <build2/variable> +#include <build2/filesystem> +#include <build2/diagnostics> + +#include <build2/bin/target> + +using namespace std; +using namespace butl; + +namespace build2 +{ + namespace cc + { + // Provide limited emulation of the rpath functionality on Windows using a + // side-by-side assembly. In a nutshell, the idea is to create an assembly + // with links to all the prerequisite DLLs. + // + // 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 (e.g., in Wine). + // + using namespace bin; + + // 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. + // + timestamp + windows_rpath_timestamp (file& t) + { + timestamp r (timestamp_nonexistent); + + for (target* pt: t.prerequisite_targets) + { + if (libs* ls = pt->is_a<libs> ()) + { + // Skip installed DLLs. + // + if (ls->path ().empty ()) + continue; + + // 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 = ls->mtime ()) > r) + r = t; + + if ((t = windows_rpath_timestamp (*ls)) > r) + r = t; + } + } + + return r; + } + + // Like *_timestamp() but actually collect the DLLs. + // + static void + rpath_dlls (set<libs*>& s, file& t) + { + for (target* pt: t.prerequisite_targets) + { + if (libs* ls = pt->is_a<libs> ()) + { + // Skip installed DLLs. + // + if (ls->path ().empty ()) + continue; + + s.insert (ls); + rpath_dlls (s, *ls); + } + } + } + + const char* + windows_manifest_arch (const string& tcpu); // windows-manifest.cxx + + // The ts argument should be the the DLLs timestamp returned by + // *_timestamp(). + // + // 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 DLLs timestamp against the assembly + // manifest file. + // + void + windows_rpath_assembly (file& t, + const string& tcpu, + timestamp ts, + bool scratch) + { + // Assembly paths and name. + // + dir_path ad (path_cast<dir_path> (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 *_dlls() call + // below which allocates memory, etc. + // + if (!scratch) + { + // The corner case here is when _timestamp() 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 (ts <= file_mtime (am)) + return; + } + + // 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 aren't/no longer are any DLLs which means + // we just need to clean things up. + // + bool empty (ts == timestamp_nonexistent); + + set<libs*> dlls; + if (!empty) + rpath_dlls (dlls, t); + + // 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)); + + if (empty) + return; + + if (s == rmdir_status::not_exist) + mkdir (ad, 3); + } + + const char* pa (windows_manifest_arch (tcpu)); + + if (verb >= 3) + text << "cat >" << am; + + try + { + ofdstream ofs (am); + + ofs << "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + << "<assembly xmlns='urn:schemas-microsoft-com:asm.v1'\n" + << " manifestVersion='1.0'>\n" + << " <assemblyIdentity name='" << an << "'\n" + << " type='win32'\n" + << " processorArchitecture='" << pa << "'\n" + << " version='0.0.0.0'/>\n"; + + scope& as (*t.root_scope ().weak_scope ()); // Amalgamation scope. + + auto link = [&as, &ad] (const path& f, const path& l) + { + auto print = [&f, &l] (const char* cmd) + { + if (verb >= 3) + text << cmd << ' ' << f << ' ' << l; + }; + + // 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 (f.sub (as.out_path ())) + mksymlink (f.relative (ad), l); + else + mksymlink (f, l); + + 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 " << l << ": " << e.what (); + } + + try + { + mkhardlink (f, l); + print ("ln"); + } + catch (const system_error& e) + { + int c (e.code ().value ()); + + if (c != EPERM && c != ENOSYS) + { + print ("ln"); + fail << "unable to create hardlink " << l << ": " << e.what (); + } + + try + { + cpfile (f, l); + print ("cp"); + } + catch (const system_error& e) + { + print ("cp"); + fail << "unable to create copy " << l << ": " << e.what (); + } + } + } + + }; + + for (libs* dll: dlls) + { + const path& dp (dll->path ()); // DLL path. + const path dn (dp.leaf ()); // DLL name. + link (dp, ad / dn); + + // Link .pdb if there is one (second member of the ad hoc group). + // + if (dll->member != nullptr && dll->member->member != nullptr) + { + file& pdb (static_cast<file&> (*dll->member->member)); + link (pdb.path (), ad / pdb.path ().leaf ()); + } + + ofs << " <file name='" << dn.string () << "'/>\n"; + } + + ofs << "</assembly>\n"; + + ofs.close (); + } + catch (const ofdstream::failure& e) + { + fail << "unable to write to " << am << ": " << e.what (); + } + } + } +} |