aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/install/operation.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/install/operation.cxx')
-rw-r--r--libbuild2/install/operation.cxx361
1 files changed, 354 insertions, 7 deletions
diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx
index 54d5b9a..ce5d24a 100644
--- a/libbuild2/install/operation.cxx
+++ b/libbuild2/install/operation.cxx
@@ -3,8 +3,15 @@
#include <libbuild2/install/operation.hxx>
+#include <sstream>
+
+#include <libbuild2/scope.hxx>
+#include <libbuild2/target.hxx>
+#include <libbuild2/context.hxx>
#include <libbuild2/variable.hxx>
+#include <libbuild2/install/utility.hxx>
+
using namespace std;
using namespace butl;
@@ -12,22 +19,356 @@ namespace build2
{
namespace install
{
+#ifndef BUILD2_BOOTSTRAP
+ context_data::
+ context_data (const path* mf)
+ : manifest_name (mf),
+ manifest_os (mf != nullptr
+ ? 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 (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.
+ //
+ // Note that we always serialize directories as top-level entries. And
+ // theoretically we can end up "splitting" a target with a directory
+ // creation. For example, if some files that belong to the target are
+ // installed into subdirectories that have not yet been created. So we
+ // have to cache the information for the current target in memory and only
+ // flush it once we see the next target (or the end).
+ //
+ // You may be wondering why not just serialize directories as target
+ // entries. While we could do that, it's not quite correct conceptually,
+ // since this would be the first of potentially many targets that caused
+ // the directory's creation. To put it another way, while files and
+ // symlinks belong to tragets, directories do not.
+ //
+ static void
+ manifest_flush_target (context_data& d, const target* tgt)
+ {
+ if (d.manifest_target != nullptr)
+ {
+ assert (!d.manifest_target_entries.empty ());
+
+ // Target name format is the same as in the structured result output.
+ //
+ ostringstream os;
+ stream_verb (os, stream_verbosity (1, 0));
+ os << *d.manifest_target;
+
+ try
+ {
+ auto& s (d.manifest_json);
+
+ s.begin_object ();
+ s.member ("type", "target");
+ s.member ("name", os.str ());
+ s.member_name ("entries");
+ s.begin_array ();
+
+ 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", p.string ());
+ s.member ("mode", e.mode);
+ }
+ else
+ {
+ s.member ("type", "symlink");
+ s.member ("path", p.string ());
+ s.member ("target", e.target.string ());
+ }
+
+ s.end_object ();
+ }
+
+ s.end_array (); // entries member
+ s.end_object (); // target object
+ }
+ catch (const json::invalid_json_output& e)
+ {
+ fail << "invalid " << d.manifest_name << " json output: " << e;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << d.manifest_name << ": " << e;
+ }
+
+ d.manifest_target_entries.clear ();
+ }
+
+ d.manifest_target = tgt;
+ }
+
+ void context_data::
+ manifest_install_d (context& ctx,
+ const target& tgt,
+ const dir_path& dir,
+ const string& mode)
+ {
+ auto& d (*static_cast<context_data*> (ctx.current_inner_odata.get ()));
+
+ if (d.manifest_name.path != nullptr)
+ {
+ try
+ {
+ auto& s (d.manifest_json);
+
+ // If we moved to the next target, flush the current one.
+ //
+ if (d.manifest_target != &tgt)
+ manifest_flush_target (d, nullptr);
+
+ s.begin_object ();
+ s.member ("type", "directory");
+ 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_name << " json output: " << e;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << d.manifest_name << ": " << e;
+ }
+ }
+ }
+
+ void context_data::
+ manifest_install_f (context& ctx,
+ const target& tgt,
+ const dir_path& dir,
+ const path& name,
+ const string& mode)
+ {
+ auto& d (*static_cast<context_data*> (ctx.current_inner_odata.get ()));
+
+ 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, mode, path ()});
+ }
+ }
+
+ void context_data::
+ manifest_install_l (context& ctx,
+ const target& tgt,
+ const path& link_target,
+ const dir_path& dir,
+ const path& link)
+ {
+ auto& d (*static_cast<context_data*> (ctx.current_inner_odata.get ()));
+
+ 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, "", link_target});
+ }
+ }
+
+ static void
+ manifest_close (context& ctx)
+ {
+ auto& d (*static_cast<context_data*> (ctx.current_inner_odata.get ()));
+
+ if (d.manifest_name.path != nullptr)
+ {
+ try
+ {
+ manifest_flush_target (d, nullptr);
+
+ d.manifest_os << '\n'; // Final newline.
+
+ if (d.manifest_ofs.is_open ())
+ {
+ d.manifest_ofs.close ();
+ d.manifest_autorm.cancel ();
+ }
+ }
+ catch (const json::invalid_json_output& e)
+ {
+ fail << "invalid " << d.manifest_name << " json output: " << e;
+ }
+ catch (const io_error& e)
+ {
+ fail << "unable to write to " << d.manifest_name << ": " << e;
+ }
+ }
+ }
+#else
+ context_data::
+ context_data (const path*)
+ {
+ }
+
+ void context_data::
+ manifest_install_d (context&,
+ const target&,
+ const dir_path&,
+ const string&)
+ {
+ }
+
+ void context_data::
+ manifest_install_f (context&,
+ const target&,
+ const dir_path&,
+ const path&,
+ const string&)
+ {
+ }
+
+ void context_data::
+ manifest_install_l (context&,
+ const target&,
+ const path&,
+ const dir_path&,
+ const path&)
+ {
+ }
+
+ static void
+ manifest_close (context&)
+ {
+ }
+#endif
+
static operation_id
- install_pre (const values& params, meta_operation_id mo, const location& l)
+ pre_install (context&,
+ const values&,
+ meta_operation_id mo,
+ const location&)
{
- if (!params.empty ())
- fail (l) << "unexpected parameters for operation install";
+ // Run update as a pre-operation, unless we are disfiguring.
+ //
+ return mo != disfigure_id ? update_id : 0;
+ }
+ static operation_id
+ pre_uninstall (context&,
+ const values&,
+ meta_operation_id mo,
+ const location&)
+ {
// Run update as a pre-operation, unless we are disfiguring.
//
return mo != disfigure_id ? update_id : 0;
}
+ static void
+ install_pre (context& ctx,
+ const values& params,
+ bool inner,
+ const location& l)
+ {
+ if (!params.empty ())
+ fail (l) << "unexpected parameters for operation install";
+
+ if (inner)
+ {
+ // See if we need to write the installation manifest.
+ //
+ // Note: go straight for the public variable pool.
+ //
+ const path* mf (
+ cast_null<path> (
+ ctx.global_scope[*ctx.var_pool.find ("config.install.manifest")]));
+
+ // 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 context_data (mf),
+ [] (void* p) {delete static_cast<context_data*> (p);});
+ }
+ }
+
+ static void
+ install_post (context& ctx, const values&, bool inner)
+ {
+ if (inner)
+ manifest_close (ctx);
+ }
+
// Note that we run both install and uninstall serially. The reason for
// this is all the fuzzy things we are trying to do like removing empty
// outer directories if they are empty. If we do this in parallel, then
// those things get racy. Also, since all we do here is creating/removing
// files, there is not going to be much speedup from doing it in parallel.
+ // There is also now the installation manifest, which relies on us
+ // installing all the filesystem entries of a target serially.
const operation_info op_install {
install_id,
@@ -39,8 +380,10 @@ namespace build2
"has nothing to install", // We cannot "be installed".
execution_mode::first,
0 /* concurrency */, // Run serially.
- &install_pre,
+ &pre_install,
nullptr,
+ &install_pre,
+ &install_post,
nullptr,
nullptr
};
@@ -64,7 +407,9 @@ namespace build2
"is not installed",
execution_mode::last,
0 /* concurrency */, // Run serially
- &install_pre,
+ &pre_uninstall,
+ nullptr,
+ nullptr,
nullptr,
nullptr,
nullptr
@@ -82,8 +427,10 @@ namespace build2
op_update.name_done,
op_update.mode,
op_update.concurrency,
- op_update.pre,
- op_update.post,
+ op_update.pre_operation,
+ op_update.post_operation,
+ op_update.operation_pre,
+ op_update.operation_post,
op_update.adhoc_match,
op_update.adhoc_apply
};