From 112f44f7f863e34d42657ad3bf14d160cc3e11e8 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 23 Mar 2023 09:17:13 +0200 Subject: Add support for relocatable installation in $install.resolve() --- libbuild2/cc/pkgconfig.cxx | 3 ++- libbuild2/install/functions.cxx | 56 +++++++++++++++++++++++++++++++++++++++-- libbuild2/install/rule.cxx | 46 ++++++++++++++++++++++++++------- libbuild2/install/utility.hxx | 13 ++++++++-- 4 files changed, 104 insertions(+), 14 deletions(-) diff --git a/libbuild2/cc/pkgconfig.cxx b/libbuild2/cc/pkgconfig.cxx index b06d488..3a94de2 100644 --- a/libbuild2/cc/pkgconfig.cxx +++ b/libbuild2/cc/pkgconfig.cxx @@ -1410,7 +1410,7 @@ namespace build2 { bool f (ldirs.empty ()); - ldirs.push_back (resolve_dir (g, d, !f /* fail_unknown */)); + ldirs.push_back (resolve_dir (g, d, {}, !f /* fail_unknown */)); if (f && ldirs.back ().empty ()) break; @@ -1419,6 +1419,7 @@ namespace build2 else ldirs.push_back (resolve_dir (g, cast (g["install.lib"]), + {}, false /* fail_unknown */)); if (!ldirs.empty () && ldirs.front ().empty ()) diff --git a/libbuild2/install/functions.cxx b/libbuild2/install/functions.cxx index c36a46e..8d1a1f3 100644 --- a/libbuild2/install/functions.cxx +++ b/libbuild2/install/functions.cxx @@ -15,18 +15,70 @@ namespace build2 { function_family f (m, "install"); + // $install.resolve([, ]) + // // Resolve potentially relative install.* value to an absolute and // normalized directory based on (other) install.* values visible from // the calling scope. // + // If rel_base is specified and is not empty, then make the resulting + // directory relative to it. If rel_base itself is relative, first + // resolve it to an absolute and normalized directory based on install.* + // values. Note that this argument is mandatory if this function is + // called during relocatable installation (install.relocatable is true). + // While you can pass empty directory to suppress this functionality, + // make sure this does not render the result non-relocatable. + // + // As an example, consider an executable that supports loading plugins + // and requires the plugin installation directory to be embedded into + // the executable during build. The common way to support relocatable + // installations for such cases is to embed a path relative to the + // executable and complete it at runtime. If you would like to always + // use the relative path, regardless of whether the installation is + // relocatable of not, then you can simply always pass rel_base, for + // example: + // + // plugin_dir = $install.resolve($install.lib, $install.bin) + // + // Alternatively, if you would like to continue using absolute paths for + // non-relocatable installations, then you can use something like this: + // + // plugin_dir = $install.resolve($install.lib, ($install.relocatable ? $install.bin : [dir_path] )) + // + // Finally, if you are unable to support relocatable installations, the + // correct way to handle this is NOT to always pass an empty path for + // rel_base but rather assert in root.build that your project does not + // support relocatable installations, for example: + // + // assert (!$install.relocatable) 'relocatable installation not supported' + // // Note that this function is not pure. // - f.insert (".resolve", false) += [] (const scope* s, dir_path d) + f.insert (".resolve", false) += [] (const scope* s, + dir_path dir, + optional rel_base) { if (s == nullptr) fail << "install.resolve() called out of scope" << endf; - return resolve_dir (*s, move (d)); + if (!rel_base) + { + const scope& rs (*s->root_scope ()); + + if (cast_false (rs["install.relocatable"])) + { + fail << "relocatable installation requires relative base " + << "directory" << + info << "pass empty relative base directory if this call does " + << "not affect installation relocatability" << + info << "or add `assert (!$install.relocatable) 'relocatable " + << "installation not supported'` before the call"; + } + } + + return resolve_dir (*s, + move (dir), + rel_base ? move (*rel_base) : dir_path ()); }; } } diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index 9f7eaac..5a8242b 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -668,24 +668,52 @@ namespace build2 return rs; } - static inline install_dirs - resolve (const target& t, dir_path d, bool fail_unknown = true) + static dir_path + resolve_dir (const scope& s, const target* t, + dir_path d, dir_path rb, + bool fail_unknown) { - return resolve (t.base_scope (), &t, move (d), fail_unknown); + install_dirs rs (resolve (s, t, move (d), fail_unknown)); + + if (rs.empty ()) + return dir_path (); + + dir_path r (move (rs.back ().dir)); + + if (!rb.empty ()) + { + dir_path b (resolve (s, t, move (rb), false).back ().dir); + + try + { + r = r.relative (b); + } + catch (const invalid_path&) + { + fail << "unable to make installation directory " << r + << " relative to " << b; + } + } + + return r; } dir_path - resolve_dir (const target& t, dir_path d, bool fail_unknown) + resolve_dir (const target& t, dir_path d, dir_path rb, bool fail_unknown) { - install_dirs r (resolve (t, move (d), fail_unknown)); - return r.empty () ? dir_path () : move (r.back ().dir); + return resolve_dir (t.base_scope (), &t, move (d), move (rb), fail_unknown); } dir_path - resolve_dir (const scope& s, dir_path d, bool fail_unknown) + resolve_dir (const scope& s, dir_path d, dir_path rb, bool fail_unknown) + { + return resolve_dir (s, nullptr, move (d), move (rb), fail_unknown); + } + + static inline install_dirs + resolve (const target& t, dir_path d, bool fail_unknown = true) { - install_dirs r (resolve (s, nullptr, move (d), fail_unknown)); - return r.empty () ? dir_path () : move (r.back ().dir); + return resolve (t.base_scope (), &t, move (d), fail_unknown); } path diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx index 7ab8114..2ba7b18 100644 --- a/libbuild2/install/utility.hxx +++ b/libbuild2/install/utility.hxx @@ -74,13 +74,22 @@ namespace build2 // and fail unless fail_unknown is false, in which case return empty // directory. // + // For rel_base semantics, see the $install.resolve() documentation. Note + // that fail_unknown does not apply to the rel_base resolution. + // // Note: implemented in rule.cxx. // LIBBUILD2_SYMEXPORT dir_path - resolve_dir (const target&, dir_path, bool fail_unknown = true); + resolve_dir (const target&, + dir_path, + dir_path rel_base = {}, + bool fail_unknown = true); LIBBUILD2_SYMEXPORT dir_path - resolve_dir (const scope&, dir_path, bool fail_unknown = true); + resolve_dir (const scope&, + dir_path, + dir_path rel_base = {}, + bool fail_unknown = true); // Resolve file installation path returning empty path if not installable. // -- cgit v1.1