// file : libbuild2/install/init.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include // command_substitute() #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace butl; namespace build2 { namespace install { // Process an install..* value replacing the -substitutions // with their actual values. Note that for now we are only doing this for // dir_path (install. variables). // // The semantics of <>-substitution is inspired by our command running // facility. In a nutshell, `<<` is an escape, unknown or unterminated // substitution is an error. // // template static inline T proc_var (const dir_path*, scope&, const T& val, const variable&) { return val; } static inline dir_path proc_var (const dir_path* prv, scope& rs, const dir_path& val, const variable& var) { if (val.string ().find ('<') == string::npos) return val; // Note: watch out for the small std::function optimization. // struct data { const dir_path* prv; const dir_path& val; const variable& var; } d {prv, val, var}; auto subst = [&d, &rs] (const string& n, string& r) { if (n == "project") { r += project (rs).string (); } else if (n == "version") { if (const auto* v = cast_null (rs.vars[rs.ctx.var_version])) r += *v; else fail << "no version variable in project " << project (rs) << info << "required in " << d.var << " value '" << d.val << "'"; } else if (n == "private") { if (d.prv != nullptr && !d.prv->empty ()) r += d.prv->string (); } else return false; return true; }; dir_path r; for (auto i (val.begin ()); i != val.end (); ++i) { auto o (*i); // Original. size_t p (o.find ('<')); if (p == string::npos) { r.combine (o, i.separator ()); continue; } string s; // Substituted. try { s = command_substitute (o, p, subst, '<', '>'); } catch (const invalid_argument& e) { fail << "invalid " << var << " value '" << val << "': " << e; } // In case of the result of the substitution may have // multiple path components. // if (path::traits_type::find_separator (s) == string::npos) { r.combine (s, i.separator ()); continue; } dir_path d; try { d = dir_path (move (s)); } catch (const invalid_path& e) { fail << "invalid path '" << e.path << "'"; } // Use the substitution's separators except for the last one. // for (auto j (d.begin ()), e (d.end ()); j != e; ) { auto c (*j); r.combine (c, ++j != e ? j.separator () : i.separator ()); } } return r; } // Set an install..* value based on config.install..* or the // default. If none of config.install.* values were specified (spec is // false), then we do omitted/delayed configuration. Note that we still // need to set all the install.* values to defaults, as if we had the // default configuration. // // If override is true, then override values that came from outer // configurations. We had to do this for paths that contain the project // name but now we use the substitution. Let's keep this // functionality for now in case we need it for something else. // // For global values we only set config.install.* variables. Non-global // values with NULL defaults are omitted. // template static void set_var (bool spec, const dir_path* prv, scope& rs, const char* name, const char* var, const CT* dv, bool override = false) { string vn; lookup l; bool global (*name == '\0'); if (spec) { vn = "config.install"; if (!global) { vn += '.'; vn += name; } vn += var; const variable& vr (rs.var_pool ().insert (move (vn))); using config::lookup_config; l = dv != nullptr ? lookup_config (rs, vr, *dv, 0 /* save_flags */, override) : (global ? lookup_config (rs, vr, nullptr) : lookup_config (rs, vr)); } if (global) return; vn = "install."; vn += name; vn += var; const variable& vr (rs.var_pool ().insert (move (vn))); value& v (rs.assign (vr)); if (spec) { if (l) v = proc_var (prv, rs, cast (l), vr); // Strip CT to T. } else { if (dv != nullptr) v = proc_var (prv, rs, *dv, vr); } } template static void set_dir (bool s, // specified const dir_path* p, // scope& rs, // root scope const char* n, // var name const T& d, // path bool o = false, // override const string& fm = string (), // file mode const string& dm = string (), // dir mode const build2::path& c = build2::path ()) // command { using build2::path; bool global (*n == '\0'); if (!global) set_var (s, p, rs, n, "", d.empty () ? nullptr : &d, o); set_var (s, p, rs, n, ".cmd", c.empty () ? nullptr : &c); set_var (s, p, rs, n, ".options", (strings*) (nullptr)); set_var (s, p, rs, n, ".mode", fm.empty () ? nullptr : &fm); set_var (s, p, rs, n, ".dir_mode", dm.empty () ? nullptr : &dm); set_var (s, p, rs, n, ".sudo", (string*) (nullptr)); // This one doesn't have config.* value (only set in a buildfile). // if (!global) rs.var_pool ().insert (string ("install.") + n + ".subdirs"); } void functions (function_map&); // functions.cxx void boot (scope& rs, const location&, module_boot_extra&) { tracer trace ("install::boot"); l5 ([&]{trace << "for " << rs;}); context& ctx (rs.ctx); // Register the install function family if this is the first instance of // the install modules. // if (!function_family::defined (ctx.functions, "install")) functions (ctx.functions); // Register our operations. // rs.insert_operation (install_id, op_install); rs.insert_operation (uninstall_id, op_uninstall); rs.insert_operation (update_for_install_id, op_update_for_install); } static const path cmd ("install"); // Default config.install.* values. // #define DIR(N, V) static const dir_path dir_##N (V) DIR (data_root, dir_path ("root")); DIR (exec_root, dir_path ("root")); DIR (sbin, dir_path ("exec_root") /= "sbin"); DIR (bin, dir_path ("exec_root") /= "bin"); DIR (lib, (dir_path ("exec_root") /= "lib") /= ""); DIR (libexec, ((dir_path ("exec_root") /= "libexec") /= "") /= ""); DIR (pkgconfig, dir_path ("lib") /= "pkgconfig"); DIR (etc, dir_path ("data_root") /= "etc"); DIR (include, (dir_path ("data_root") /= "include") /= ""); DIR (share, dir_path ("data_root") /= "share"); DIR (data, (dir_path ("share") /= "") /= ""); DIR (doc, ((dir_path ("share") /= "doc") /= "") /= ""); DIR (legal, dir_path ("doc")); DIR (man, dir_path ("share") /= "man"); DIR (man1, dir_path ("man") /= "man1"); #undef DIR static const group_rule group_rule_ (true /* see_through_only */); bool init (scope& rs, scope& bs, const location& l, bool first, bool, module_init_extra&) { tracer trace ("install::init"); if (!first) { warn (l) << "multiple install module initializations"; return true; } l5 ([&]{trace << "for " << rs;}); // Enter module variables. // auto& vp (rs.var_pool ()); // Note that the set_dir() calls below enter some more. // { // The install variable is a path, not dir_path, since it can be used // to both specify the target directory (to install with the same file // name) or target file (to install with a different name). And the // way we distinguish between the two is via the presence/absence of // the trailing directory separator. // vp.insert ("install", variable_visibility::target); vp.insert ("for_install", variable_visibility::prereq); vp.insert ("install.mode"); vp.insert ("install.subdirs"); } // Environment. // // Installation may involve executing the following programs: // // install // // GNU coreutils install recognizes the SIMPLE_BACKUP_SUFFIX and // VERSION_CONTROL variables but they only matter with --backup which // we do not specify and assume unlikely to be specified via .options. // // FreeBSD install recognizes STRIPBIN and DONTSTRIP variables that // only matter with -s which we do not specify but which could be // specified with .options. NetBSD and OpenBSD use STRIP (Mac OS man // page doesn't list anything). // // sudo // // While sudo has a bunch of SUDO_* variables, none of them appear to // matter (either not used in the modes that we invoke sudo in or do // not affect the result). // // ln, rm, rmdir // // GNU coreutils ln recognizes the SIMPLE_BACKUP_SUFFIX and // VERSION_CONTROL variables but they only matter with --backup which // we do not specify. // #if defined(__FreeBSD__) config::save_environment (rs, {"STRIPBIN", "DONTSTRIP"}); #elif defined(__NetBSD__) || \ defined(__OpenBSD__) config::save_environment (rs, "STRIP"); #endif // Register our rules. // { const auto& ar (alias_rule::instance); const auto& dr (fsdir_rule::instance); const auto& fr (file_rule::instance); const auto& gr (group_rule_); bs.insert_rule (perform_install_id, "install.alias", ar); bs.insert_rule (perform_uninstall_id, "uninstall.alias", ar); bs.insert_rule (perform_install_id, "install.fsdir", dr); bs.insert_rule (perform_uninstall_id, "install.fsdir", dr); bs.insert_rule (perform_install_id, "install.file", fr); bs.insert_rule (perform_uninstall_id, "uninstall.file", fr); bs.insert_rule (perform_install_id, "install.file", gr); bs.insert_rule (perform_uninstall_id, "uninstall.file", gr); // Register the fallback file rule for the update-for-install // operation, similar to update. // rs.global_scope ().insert_rule ( perform_install_id, "install.file", file_rule::instance); } // Configuration. // // config.install.scope // // We do not install prerequisites (for example, shared libraries) of // targets (for example, executables) that belong to projects outside of // this scope. Valid values are: // // project -- project scope // strong -- strong amalgamation // weak -- weak amalgamation // global -- all projects (default) // // Note: can only be specified as a global override. // { auto& v (vp.insert ("config.install.scope")); // If specified, verify it is a global override. // if (lookup l = rs[v]) { if (!l.belongs (rs.global_scope ())) fail << "config.install.scope must be a global override" << info << "specify !config.install.scope=..."; } config::unsave_variable (rs, v); } // Installation directories. // // Note that we don't use any defaults for root -- the location // must be explicitly specified or the installer will complain // if and when we try to install. // { using build2::path; using config::lookup_config; using config::specified_config; bool s (specified_config (rs, "install")); // Adjust module priority so that the (numerous) config.install.* // values are saved at the end of config.build. // if (s) config::save_module (rs, "install", INT32_MAX); // Support for private install (aka poor man's Flatpack). // const dir_path* p; { auto& var (vp.insert ( "install.private")); auto& cvar (vp.insert ("config.install.private")); value& v (rs.assign (var)); if (s) { if (lookup l = lookup_config (rs, cvar, nullptr)) v = cast (l); } if ((p = cast_null (v)) != nullptr) { if (p->absolute ()) fail << "absolute directory " << *p << " in install.private"; } } // Support for chroot'ed install (aka DESTDIR). // { auto& var (vp.insert ( "install.chroot")); auto& cvar (vp.insert ("config.install.chroot")); value& v (rs.assign (var)); if (s) { if (lookup l = lookup_config (rs, cvar, nullptr)) v = cast (l); // Strip abs_dir_path. } } // Global config.install.* values. // set_dir (s, p, rs, "", abs_dir_path (), false, "644", "755", cmd); set_dir (s, p, rs, "root", abs_dir_path ()); set_dir (s, p, rs, "data_root", dir_data_root); set_dir (s, p, rs, "exec_root", dir_exec_root, false, "755"); set_dir (s, p, rs, "sbin", dir_sbin); set_dir (s, p, rs, "bin", dir_bin); set_dir (s, p, rs, "lib", dir_lib); set_dir (s, p, rs, "libexec", dir_libexec); set_dir (s, p, rs, "pkgconfig", dir_pkgconfig, false, "644"); set_dir (s, p, rs, "etc", dir_etc); set_dir (s, p, rs, "include", dir_include); set_dir (s, p, rs, "share", dir_share); set_dir (s, p, rs, "data", dir_data); set_dir (s, p, rs, "doc", dir_doc); set_dir (s, p, rs, "legal", dir_legal); set_dir (s, p, rs, "man", dir_man); set_dir (s, p, rs, "man1", dir_man1); } // Configure "installability" for built-in target types. // install_path (bs, dir_path ("bin")); install_path (bs, dir_path ("doc")); install_path (bs, dir_path ("legal")); install_path (bs, dir_path ("man")); install_path (bs, dir_path ("man1")); return true; } static const module_functions mod_functions[] = { {"install", &boot, &init}, {nullptr, nullptr, nullptr} }; const module_functions* build2_install_load () { return mod_functions; } } }