// file : build/install/rule.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2015 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #include <build/install/rule> #include <butl/process> #include <butl/filesystem> #include <build/scope> #include <build/target> #include <build/algorithm> #include <build/diagnostics> #include <build/config/utility> using namespace std; using namespace butl; namespace build { namespace install { // Lookup the install or install.* variable. Return NULL if // not found or if the value is the special 'false' name (which // means do not install). T is either scope or target. // template <typename T> static const dir_path* lookup (T& t, const string& var) { auto l (t[var]); if (!l) return nullptr; const dir_path& r (as<dir_path> (*l)); return r.simple () && r.string () == "false" ? nullptr : &r; } match_result rule:: match (action a, target& t, const std::string&) const { // First determine if this target should be installed (called // "installable" for short). // match_result mr (t, lookup (t, "install") != nullptr); // If this is the update pre-operation, change the recipe action // to (update, 0) (i.e., "unconditional update"). // if (mr.bvalue && a.operation () == update_id) mr.recipe_action = action (a.meta_operation (), update_id); return mr; } recipe rule:: apply (action a, target& t, const match_result& mr) const { if (!mr.bvalue) // Not installable. return noop_recipe; // Ok, if we are here, then this means: // // 1. This target is installable. // 2. The action is either // a. (perform, install, 0) or // b. (*, update, install) // // In both cases, the next step is to search, match, and collect // all the installable prerequisites. // // @@ Perhaps if [noinstall] will be handled by the // group_prerequisite_members machinery, then we can just // run standard search_and_match()? Will need an indicator // that it was forced (e.g., [install]) for filter() below. // for (prerequisite_member p: group_prerequisite_members (a, t)) { // @@ This is where we will handle [noinstall]. // // Let a customized rule have its say. // // @@ This will be skipped if forced with [install]. // if (!filter (a, t, p)) continue; target& pt (p.search ()); build::match (a, pt); // If the matched rule returned noop_recipe, then the target // state will be set to unchanged as an optimization. Use this // knowledge to optimize things on our side as well since this // will help a lot in case of any static installable content // (headers, documentation, etc). // if (pt.state () != target_state::unchanged) t.prerequisite_targets.push_back (&pt); else unmatch (a, pt); // No intent to execute. } // This is where we diverge depending on the operation. In the // update pre-operation, we need to make sure that this target // as well as all its installable prerequisites are up to date. // if (a.operation () == update_id) { // Save the prerequisite targets that we found since the // call to match_delegate() below will wipe them out. // target::prerequisite_targets_type p; if (!t.prerequisite_targets.empty ()) p.swap (t.prerequisite_targets); // Find the "real" update rule, that is, the rule that would // have been found if we signalled that we do not match from // match() above. // recipe d (match_delegate (a, t).first); // If we have no installable prerequsites, then simply redirect // to it. // if (p.empty ()) return d; // Ok, the worst case scenario: we need to cause update of // prerequisite targets and also delegate to the real update. // return [pt = move (p), dr = move (d)] (action a, target& t) mutable -> target_state { // Do the target update first. // target_state r (execute_delegate (dr, a, t)); // Swap our prerequisite targets back in and execute. // t.prerequisite_targets.swap (pt); r |= execute_prerequisites (a, t); pt.swap (t.prerequisite_targets); // In case we get re-executed. return r; }; } else return &perform_install; } struct install_dir { dir_path dir; string cmd; //@@ VAR type const_strings_value options {nullptr}; string mode; string dir_mode; }; // install -d <dir> // static void install (const install_dir& base, const dir_path& d) { path reld (relative (d)); cstrings args {base.cmd.c_str (), "-d"}; if (base.options.d != nullptr) //@@ VAR config::append_options (args, base.options); args.push_back ("-m"); args.push_back (base.dir_mode.c_str ()); args.push_back (reld.string ().c_str ()); args.push_back (nullptr); if (verb >= 2) print_process (args); else if (verb) text << "install " << d; try { process pr (args.data ()); if (!pr.wait ()) throw failed (); } catch (const process_error& e) { error << "unable to execute " << args[0] << ": " << e.what (); if (e.child ()) exit (1); throw failed (); } } // install <file> <dir> // static void install (const install_dir& base, file& t) { path reld (relative (base.dir)); path relf (relative (t.path ())); cstrings args {base.cmd.c_str ()}; if (base.options.d != nullptr) //@@ VAR config::append_options (args, base.options); args.push_back ("-m"); args.push_back (base.mode.c_str ()); args.push_back (relf.string ().c_str ()); args.push_back (reld.string ().c_str ()); args.push_back (nullptr); if (verb >= 2) print_process (args); else if (verb) text << "install " << t; try { process pr (args.data ()); if (!pr.wait ()) throw failed (); } catch (const process_error& e) { error << "unable to execute " << args[0] << ": " << e.what (); if (e.child ()) exit (1); throw failed (); } } // Resolve installation directory name to absolute directory path, // creating leading directories as necessary. // static install_dir resolve (scope& s, dir_path d, const string* var = nullptr) { install_dir r; if (d.absolute ()) { d.normalize (); // Make sure it already exists (this will normally be // install.root with everything else defined in term of it). // if (!dir_exists (d)) fail << "installation directory " << d << " does not exist"; } else { // If it is relative, then the first component is treated // as the installation directory name, e.g., bin, sbin, lib, // etc. Look it up and recurse. // const string& sn (*d.begin ()); const string var ("install." + sn); if (const dir_path* dn = lookup (s, var)) { r = resolve (s, *dn, &var); d = r.dir / dir_path (++d.begin (), d.end ()); d.normalize (); if (!dir_exists (d)) install (r, d); // install -d } else fail << "unknown installation directory name " << sn << info << "did you forget to specify config." << var << "?"; } r.dir = move (d); // Override components in install_dir if we have our own. // if (var != nullptr) { if (auto l = s[*var + ".cmd"]) r.cmd = as<string> (*l); if (auto l = s[*var + ".mode"]) r.mode = as<string> (*l); if (auto l = s[*var + ".dir_mode"]) r.dir_mode = as<string> (*l); if (auto l = s[*var + ".options"]) r.options = as<strings> (*l); } // Set defaults for unspecified components. // if (r.cmd.empty ()) r.cmd = "install"; if (r.mode.empty ()) r.mode = "644"; if (r.dir_mode.empty ()) r.dir_mode = "755"; return r; } target_state rule:: perform_install (action a, target& t) { file& ft (static_cast<file&> (t)); assert (!ft.path ().empty ()); // Should have been assigned by update. // First handle installable prerequisites. // target_state r (execute_prerequisites (a, t)); // Resolve and, if necessary, create target directory. // install_dir d ( resolve (t.base_scope (), as<dir_path> (*t["install"]))); // We know it's there. // Override mode if one was specified. // if (auto l = t["install.mode"]) d.mode = as<string> (*l); install (d, ft); return (r |= target_state::changed); } } }