// file : libbuild2/install/operation.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include #include #include #include #include #include using namespace std; using namespace butl; 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 (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. // // 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 (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 (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 (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 (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 pre_install (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 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 ( 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 (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, 0, "install", "install", "installing", "installed", "has nothing to install", // We cannot "be installed". execution_mode::first, 0 /* concurrency */, // Run serially. &pre_install, nullptr, &install_pre, &install_post, nullptr, nullptr }; // Note that we run update as a pre-operation, just like install. Which // may seem bizarre at first. We do it to obtain the exact same dependency // graph as install so that we uninstall exactly the same set of files as // install would install. Note that just matching the rules without // executing them may not be enough: for example, a presence of an ad hoc // group member may only be discovered after executing the rule (e.g., VC // link.exe only creates a DLL's import library if there are any exported // symbols). // const operation_info op_uninstall { uninstall_id, 0, "uninstall", "uninstall", "uninstalling", "uninstalled", "is not installed", execution_mode::last, 0 /* concurrency */, // Run serially &pre_uninstall, nullptr, nullptr, nullptr, nullptr, nullptr }; // Also the explicit update-for-install operation alias. // const operation_info op_update_for_install { update_id, // Note: not update_for_install_id. install_id, op_update.name, op_update.name_do, op_update.name_doing, op_update.name_did, op_update.name_done, op_update.mode, op_update.concurrency, op_update.pre_operation, op_update.post_operation, op_update.operation_pre, op_update.operation_post, op_update.adhoc_match, op_update.adhoc_apply }; } }