aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-03-23 06:18:26 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-03-23 06:18:26 +0200
commit5a763a5552184090602f0a9303b9266f5412f020 (patch)
tree0cf1fb0189157c4d02976156007fe4ee6ad7f1d1
parentf62b8809d827a0474284e75ad5724cf201ed83b1 (diff)
Add support for relocatable installation manifest
-rw-r--r--libbuild2/install/init.cxx10
-rw-r--r--libbuild2/install/operation.cxx99
-rw-r--r--libbuild2/install/operation.hxx9
-rw-r--r--libbuild2/install/rule.cxx25
-rw-r--r--libbuild2/install/rule.hxx3
-rw-r--r--libbuild2/install/utility.hxx12
6 files changed, 116 insertions, 42 deletions
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<filesystem_entry*> 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<path> ("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 <libbuild2/context.hxx>
#include <libbuild2/variable.hxx>
+#include <libbuild2/install/utility.hxx>
+
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<bool> (rs["install.relocatable"]))
+ {
+ // Note: install.root is abs_dir_path so absolute and normalized.
+ //
+ const dir_path* root (cast_null<dir_path> (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<install_context_data*> (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<install_context_data*> (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<install_context_data*> (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<install_context_data*> (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<path> (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<install_context_data*> (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_entry> 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 <typename P>
- static inline P
- chroot_path (const scope& rs, const P& p)
- {
- if (const dir_path* d = cast_null<dir_path> (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<bool> (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 <link_target> <base>/<link>
//
+ // Note that <link_target> 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 <typename P>
+ inline P
+ chroot_path (const scope& rs, const P& p)
+ {
+ assert (p.absolute ());
+ const dir_path* d (cast_null<dir_path> (rs["install.chroot"]));
+ return d != nullptr ? *d / p.leaf (p.root_directory ()) : p;
+ }
}
}