From 1dc3b7357db363c51e449adf0a3779924fb13e01 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 4 Sep 2019 13:32:07 +0200 Subject: Add support for native shared library versioning on Linux Now we can do: lib{foo}: bin.lib.version = linux@1.2 And end up with libfoo.so.1.2 libfoo.so.1 -> libfoo.so.1.2 --- libbuild2/cc/link-rule.cxx | 134 +++++++++++++++++++++++++++++++++++++-------- libbuild2/target.cxx | 12 +++- libbuild2/target.hxx | 10 +++- 3 files changed, 126 insertions(+), 30 deletions(-) (limited to 'libbuild2') diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 110a992..26a9955 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -327,6 +327,7 @@ namespace build2 // Figure out the version. // string ver; + bool verp (true); // Platform-specific. using verion_map = map; if (const verion_map* m = cast_null (t["bin.lib.version"])) { @@ -346,16 +347,13 @@ namespace build2 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 ()) + { + verp = false; 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. @@ -389,9 +387,30 @@ namespace build2 // Clean pattern. // + // Note that it's quite loose and we do additional filtering at the + // match site. + // + // Note that it is used to "catch" not only old versions but also old + // load suffixes. + // + // @@ Won't we have an issue if we have, say, libfoo and libfoo-io + // in the same directory? + // path cp (b); - cp += "?*"; // Don't match empty (like the libfoo.so symlink). + cp += "?*"; // For libfoo-1.2.so (don't match empty). append_ext (cp); +#if 0 + // @@ This is too loose, it matches all kinds of unexpected stuff: + // utility libraries (libfoo.so.u.a), object files (foo.dll.obj). + // + // Maybe `libfoo*.so.[0-9]*` would have done the trick? But that + // won't catch old load suffixes (@@ test this, BTW). Maybe + // adjust dependening on whether there is one? + // + // Note that this will still match .d. + // + cp += '*'; // For libfoo.so.1.2. +#endif // On Windows the real path is to libs{} and the link path is empty. // Note that we still need to derive the import library path. @@ -436,13 +455,79 @@ namespace build2 } } - if (!ver.empty ()) - b += ver; + // Append version and derive the real name. + // + const path* re (nullptr); + if (ver.empty () || !verp) + { + if (!ver.empty ()) + b += ver; + + re = &t.derive_path (move (b)); + } + else + { + // Parse the next version component in the X.Y.Z version form. + // + // Note that we don't bother verifying components are numeric assuming + // the user knows what they are doing (one can sometimes see versions + // with non-numeric components). + // + auto next = [&ver, + b = size_t (0), + e = size_t (0)] (const char* what = nullptr) mutable + { + if (size_t n = next_word (ver, b, e, '.')) + return string (ver, b, n); + + if (what != nullptr) + fail << "missing " << what << " in shared library version '" + << ver << "'" << endf; + + return string (); + }; + + if (tclass == "linux") + { + // On Linux the shared library version has the MAJOR.MINOR[.EXTRA] + // form where MAJOR is incremented for backwards-incompatible ABI + // changes, MINOR -- for backwards-compatible, and optional EXTRA + // has no specific meaning and can be used as some sort of release + // or sequence number (e.g., if the ABI has not changed). + // + string ma (next ("major component")); + string mi (next ("minor component")); + string ex (next ()); + + // The SONAME is libfoo.so.MAJOR + // + so = b; + append_ext (so); + so += '.'; so += ma; + + // If we have EXTRA, then make libfoo.so.MAJOR.MINOR to be the + // intermediate name. + // + if (!ex.empty ()) + { + in = b; + append_ext (in); + in += '.'; in += ma; + in += '.'; in += mi; + } - const path& re (t.derive_path (move (b))); + // Add the whole version as the extra extension(s). + // + re = &t.derive_path (move (b), + nullptr /* default_ext */, + ver.c_str () /* extra_ext */); + } + else + fail << tclass << "-specific bin.lib.version not yet supported"; + } return libs_paths { - move (lk), move (ld), move (so), move (in), &re, move (cp)}; + move (lk), move (ld), move (so), move (in), re, move (cp)}; } // Look for binary-full utility library recursively until we hit a @@ -2594,20 +2679,21 @@ namespace build2 { if (!interm) { - // Filter out paths that have one of the current paths as a - // prefix. + if (m.extension () == "d") // Strip .d. + m.make_base (); + + // Filter out paths that match one of the current paths. // - auto test = [&m] (const path& p) - { - const string& s (p.string ()); - return s.empty () || m.string ().compare (0, s.size (), s) != 0; - }; - - if (test (*paths.real) && - test ( paths.interm) && - test ( paths.soname) && - test ( paths.load) && - test ( paths.link)) + // Yes, we are basically ad hoc-excluding things that break. + // Maybe we should use something more powerful for the pattern, + // such as regex? We could have a filesystem pattern which we + // then filter against a regex pattern? + // + if (m != *paths.real && + m != paths.interm && + m != paths.soname && + m != paths.load && + m != paths.link) { try_rmfile (m); try_rmfile (m + ".d"); diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index 22f5e66..e4218ed 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -659,7 +659,7 @@ namespace build2 } const path& path_target:: - derive_path (const char* de, const char* np, const char* ns) + derive_path (const char* de, const char* np, const char* ns, const char* ee) { path_type p (dir); @@ -674,11 +674,11 @@ namespace build2 if (ns != nullptr) p += ns; - return derive_path (move (p), de); + return derive_path (move (p), de, ee); } const path& path_target:: - derive_path (path_type p, const char* de) + derive_path (path_type p, const char* de, const char* ee) { // Derive and add the extension if any. // @@ -692,6 +692,12 @@ namespace build2 } } + if (ee != nullptr) + { + p += '.'; + p += ee; + } + path (move (p)); return path_; } diff --git a/libbuild2/target.hxx b/libbuild2/target.hxx index d543da8..5870be4 100644 --- a/libbuild2/target.hxx +++ b/libbuild2/target.hxx @@ -1548,7 +1548,8 @@ namespace build2 // // If name_prefix is not NULL, add it before the name part and after the // directory. Similarly, if name_suffix is not NULL, add it after the name - // part and before the extension. + // part and before the extension. And if extra_ext is not NULL, then add + // it as an extra extension (think libfoo.so.1.2.3). // // Finally, if the path was already assigned to this target, then this // function verifies that the two are the same. @@ -1556,13 +1557,16 @@ namespace build2 const path_type& derive_path (const char* default_ext = nullptr, const char* name_prefix = nullptr, - const char* name_suffix = nullptr); + const char* name_suffix = nullptr, + const char* extra_ext = nullptr); // This version can be used to derive the path from another target's path // by adding another extension. // const path_type& - derive_path (path_type base, const char* default_ext = nullptr); + derive_path (path_type base, + const char* default_ext = nullptr, + const char* extra_ext = nullptr); // As above but only derives (and returns) the extension (empty means no // extension used). -- cgit v1.1