From 5a763a5552184090602f0a9303b9266f5412f020 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 23 Mar 2023 06:18:26 +0200 Subject: Add support for relocatable installation manifest --- libbuild2/install/init.cxx | 10 ++++- libbuild2/install/operation.cxx | 99 +++++++++++++++++++++++++++++++++-------- libbuild2/install/operation.hxx | 9 ++-- libbuild2/install/rule.cxx | 25 +++-------- libbuild2/install/rule.hxx | 3 ++ libbuild2/install/utility.hxx | 12 +++++ 6 files changed, 116 insertions(+), 42 deletions(-) (limited to 'libbuild2/install') diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index a3155d1..7962171 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -480,7 +480,8 @@ namespace build2 // variable can only be specified as a global override. (While it // could be handy to save this varible in config.build in some // situations, supporting this will complicate the global override - // case). + // case). Note that as a result the manifest file path may not be + // specified in terms of the config.install.* values. // // Note also that the manifest is produced even in the dry-run mode. // However, in this case no directory creation is tracked. @@ -530,6 +531,13 @@ namespace build2 // vector entries; // }; // + // If installation is relocatable (see config.install.relocatable) and + // the installation manifest file path is inside config.install.root + // (including chroot), then absolute filesystem_entry::path's are + // saved as relative to the manifest file's directory (note that + // symlink_entry::target cannot be absolute in relocatable + // installation). + // { auto& v (vp.insert ("config.install.manifest")); diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx index 32da60f..95b4381 100644 --- a/libbuild2/install/operation.cxx +++ b/libbuild2/install/operation.cxx @@ -10,6 +10,8 @@ #include #include +#include + using namespace std; using namespace butl; @@ -20,13 +22,67 @@ namespace build2 #ifndef BUILD2_BOOTSTRAP install_context_data:: install_context_data (const path* mf) - : manifest_file (mf), + : manifest_name (mf), manifest_os (mf != nullptr - ? open_file_or_stdout (manifest_file, manifest_ofs) + ? open_file_or_stdout (manifest_name, manifest_ofs) : manifest_ofs), manifest_autorm (manifest_ofs.is_open () ? *mf : path ()), manifest_json (manifest_os, 0 /* indentation */) { + if (manifest_ofs.is_open ()) + { + manifest_file = *mf; + manifest_file.complete (); + manifest_file.normalize (); + } + } + + static path + relocatable_path (install_context_data& d, const target& t, path p) + { + // This is both inefficient (re-detecting relocatable manifest for every + // path) and a bit dirty (if multiple projects are being installed with + // different install.{relocatable,root} values, we may end up producing + // some paths relative and some absolute). But doing either of these + // properly is probably not worth the extra complexity. + // + if (!d.manifest_file.empty ()) // Not stdout. + { + const scope& rs (t.root_scope ()); + + if (cast_false (rs["install.relocatable"])) + { + // Note: install.root is abs_dir_path so absolute and normalized. + // + const dir_path* root (cast_null (rs["install.root"])); + if (root == nullptr) + fail << "unknown installation root directory in " << rs << + info << "did you forget to specify config.install.root?"; + + // The manifest path would include chroot so if used, we need to add + // it to root and the file path (we could also strip it, but then + // making it absolute gets tricky on Windows). + // + dir_path md (d.manifest_file.directory ()); + + if (md.sub (chroot_path (rs, *root))) // Inside installation root. + { + p = chroot_path (rs, p); + try + { + p = p.relative (md); + } + catch (const invalid_path&) + { + fail << "unable to make filesystem entry path " << p + << " relative to " << md << + info << "required for relocatable installation manifest"; + } + } + } + } + + return p; } // Serialize current target and, if tgt is not NULL, start the new target. @@ -69,19 +125,21 @@ namespace build2 for (const auto& e: d.manifest_target_entries) { + path p (relocatable_path (d, *d.manifest_target, move (e.path))); + s.begin_object (); if (e.target.empty ()) { s.member ("type", "file"); - s.member ("path", e.path); + s.member ("path", p.string ()); s.member ("mode", e.mode); } else { s.member ("type", "symlink"); - s.member ("path", e.path); - s.member ("target", e.target); + s.member ("path", p.string ()); + s.member ("target", e.target.string ()); } s.end_object (); @@ -92,11 +150,11 @@ namespace build2 } catch (const json::invalid_json_output& e) { - fail << "invalid " << d.manifest_file << " json output: " << e; + fail << "invalid " << d.manifest_name << " json output: " << e; } catch (const io_error& e) { - fail << "unable to write to " << d.manifest_file << ": " << e; + fail << "unable to write to " << d.manifest_name << ": " << e; } d.manifest_target_entries.clear (); @@ -114,7 +172,7 @@ namespace build2 auto& d ( *static_cast (ctx.current_inner_odata.get ())); - if (d.manifest_file.path != nullptr) + if (d.manifest_name.path != nullptr) { try { @@ -127,17 +185,17 @@ namespace build2 s.begin_object (); s.member ("type", "directory"); - s.member ("path", dir.string ()); + s.member ("path", relocatable_path (d, tgt, dir).string ()); s.member ("mode", mode); s.end_object (); } catch (const json::invalid_json_output& e) { - fail << "invalid " << d.manifest_file << " json output: " << e; + fail << "invalid " << d.manifest_name << " json output: " << e; } catch (const io_error& e) { - fail << "unable to write to " << d.manifest_file << ": " << e; + fail << "unable to write to " << d.manifest_name << ": " << e; } } } @@ -152,13 +210,13 @@ namespace build2 auto& d ( *static_cast (ctx.current_inner_odata.get ())); - if (d.manifest_file.path != nullptr) + if (d.manifest_name.path != nullptr) { if (d.manifest_target != &tgt) manifest_flush_target (d, &tgt); d.manifest_target_entries.push_back ( - manifest_target_entry {(dir / name).string (), mode, ""}); + manifest_target_entry {dir / name, mode, path ()}); } } @@ -172,14 +230,13 @@ namespace build2 auto& d ( *static_cast (ctx.current_inner_odata.get ())); - if (d.manifest_file.path != nullptr) + if (d.manifest_name.path != nullptr) { if (d.manifest_target != &tgt) manifest_flush_target (d, &tgt); d.manifest_target_entries.push_back ( - manifest_target_entry { - (dir / link).string (), "", link_target.string ()}); + manifest_target_entry {dir / link, "", link_target}); } } @@ -189,7 +246,7 @@ namespace build2 auto& d ( *static_cast (ctx.current_inner_odata.get ())); - if (d.manifest_file.path != nullptr) + if (d.manifest_name.path != nullptr) { try { @@ -205,11 +262,11 @@ namespace build2 } catch (const json::invalid_json_output& e) { - fail << "invalid " << d.manifest_file << " json output: " << e; + fail << "invalid " << d.manifest_name << " json output: " << e; } catch (const io_error& e) { - fail << "unable to write to " << d.manifest_file << ": " << e; + fail << "unable to write to " << d.manifest_name << ": " << e; } } } @@ -291,6 +348,10 @@ namespace build2 const variable& var (*ctx.var_pool.find ("config.install.manifest")); const path* mf (cast_null (ctx.global_scope[var])); + // Note that we cannot calculate whether the manifest should use + // relocatable (relative) paths once here since we don't know the + // value of config.install.root. + ctx.current_inner_odata = context::current_data_ptr ( new install_context_data (mf), [] (void* p) {delete static_cast (p);}); diff --git a/libbuild2/install/operation.hxx b/libbuild2/install/operation.hxx index 71bdcba..4983976 100644 --- a/libbuild2/install/operation.hxx +++ b/libbuild2/install/operation.hxx @@ -27,7 +27,8 @@ namespace build2 struct install_context_data { #ifndef BUILD2_BOOTSTRAP - path_name manifest_file; + path manifest_file; // Absolute and normalized, empty if `-`. + path_name manifest_name; // Original path/name. ofdstream manifest_ofs; ostream& manifest_os; auto_rmfile manifest_autorm; @@ -35,9 +36,9 @@ namespace build2 const target* manifest_target = nullptr; // Target being installed. struct manifest_target_entry { - string path; - string mode; - string target; + build2::path path; + string mode; + build2::path target; }; vector manifest_target_entries; #endif diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index a3fa5ee..9f7eaac 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -755,24 +755,6 @@ namespace build2 return s; } - // Given an abolute path return its chroot'ed version, if any, accoring to - // install.chroot. - // - template - static inline P - chroot_path (const scope& rs, const P& p) - { - if (const dir_path* d = cast_null (rs["install.chroot"])) - { - dir_path r (p.root_directory ()); - assert (!r.empty ()); // Must be absolute. - - return *d / p.leaf (r); - } - - return p; - } - void file_rule:: install_d (const scope& rs, const install_dir& base, @@ -942,6 +924,13 @@ namespace build2 { context& ctx (rs.ctx); + if (link_target.absolute () && + cast_false (rs["install.relocatable"])) + { + fail << "absolute symlink target " << link_target.string () + << " in relocatable installation"; + } + dir_path chd (chroot_path (rs, base.dir)); path rell (relative (chd)); diff --git a/libbuild2/install/rule.hxx b/libbuild2/install/rule.hxx index eb8addf..b0dabe5 100644 --- a/libbuild2/install/rule.hxx +++ b/libbuild2/install/rule.hxx @@ -221,6 +221,9 @@ namespace build2 // // ln -s / // + // Note that must not be absolute if relocatable + // installation is requested (config.install.relocatable). + // // Note that the target argument only specifies which target this // symlink "belongs" to. // diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx index 530a9d7..7ab8114 100644 --- a/libbuild2/install/utility.hxx +++ b/libbuild2/install/utility.hxx @@ -86,6 +86,18 @@ namespace build2 // LIBBUILD2_SYMEXPORT path resolve_file (const file&); // rule.cxx + + // Given an abolute path return its chroot'ed version, if any, accoring to + // install.chroot. + // + template + inline P + chroot_path (const scope& rs, const P& p) + { + assert (p.absolute ()); + const dir_path* d (cast_null (rs["install.chroot"])); + return d != nullptr ? *d / p.leaf (p.root_directory ()) : p; + } } } -- cgit v1.1