diff options
Diffstat (limited to 'libbuild2/install/init.cxx')
-rw-r--r-- | libbuild2/install/init.cxx | 374 |
1 files changed, 310 insertions, 64 deletions
diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index 677ee07..3df912f 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -3,7 +3,7 @@ #include <libbuild2/install/init.hxx> -#include <libbutl/command.mxx> // command_substitute() +#include <libbutl/command.hxx> // command_substitute() #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> @@ -166,6 +166,8 @@ namespace build2 bool global (*name == '\0'); + auto& vp (rs.var_pool (true /* default */)); // All qualified. + if (spec) { vn = "config.install"; @@ -175,7 +177,7 @@ namespace build2 vn += name; } vn += var; - const variable& vr (rs.var_pool ().insert<CT> (move (vn))); + const variable& vr (vp.insert<CT> (move (vn))); using config::lookup_config; @@ -192,7 +194,7 @@ namespace build2 vn = "install."; vn += name; vn += var; - const variable& vr (rs.var_pool ().insert<T> (move (vn))); + const variable& vr (vp.insert<T> (move (vn))); value& v (rs.assign (vr)); @@ -236,7 +238,7 @@ namespace build2 // This one doesn't have config.* value (only set in a buildfile). // if (!global) - rs.var_pool ().insert<bool> (string ("install.") + n + ".subdirs"); + rs.var_pool (true).insert<bool> (string ("install.") + n + ".subdirs"); } void @@ -250,6 +252,20 @@ namespace build2 context& ctx (rs.ctx); + // Enter module variables (note that init() below enters 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. + // + // Plus it can have the special true/false values when acting as an + // operation variable. + // + auto& ovar (rs.var_pool ().insert<path> ("install", + variable_visibility::target)); + // Register the install function family if this is the first instance of // the install modules. // @@ -258,9 +274,9 @@ namespace build2 // 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); + rs.insert_operation (install_id, op_install, &ovar); + rs.insert_operation (uninstall_id, op_uninstall, &ovar); + rs.insert_operation (update_for_install_id, op_update_for_install, &ovar); } static const path cmd ("install"); @@ -269,24 +285,26 @@ namespace build2 // #define DIR(N, V) static const dir_path dir_##N (V) - DIR (data_root, dir_path ("root")); - DIR (exec_root, dir_path ("root")); + 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") /= "<private>"); - DIR (libexec, ((dir_path ("exec_root") /= "libexec") /= "<private>") /= "<project>"); - DIR (pkgconfig, dir_path ("lib") /= "pkgconfig"); + DIR (sbin, dir_path ("exec_root") /= "sbin"); + DIR (bin, dir_path ("exec_root") /= "bin"); + DIR (lib, (dir_path ("exec_root") /= "lib") /= "<private>"); + DIR (libexec, ((dir_path ("exec_root") /= "libexec") /= "<private>") /= "<project>"); + DIR (pkgconfig, dir_path ("lib") /= "pkgconfig"); - DIR (etc, dir_path ("data_root") /= "etc"); - DIR (include, (dir_path ("data_root") /= "include") /= "<private>"); - DIR (share, dir_path ("data_root") /= "share"); - DIR (data, (dir_path ("share") /= "<private>") /= "<project>"); + DIR (etc, dir_path ("data_root") /= "etc"); + DIR (include, (dir_path ("data_root") /= "include") /= "<private>"); + DIR (include_arch, dir_path ("include")); + DIR (share, dir_path ("data_root") /= "share"); + DIR (data, (dir_path ("share") /= "<private>") /= "<project>"); + DIR (buildfile, ((dir_path ("share") /= "build2") /= "export") /= "<project>"); - DIR (doc, ((dir_path ("share") /= "doc") /= "<private>") /= "<project>"); - DIR (legal, dir_path ("doc")); - DIR (man, dir_path ("share") /= "man"); - DIR (man1, dir_path ("man") /= "man1"); + DIR (doc, ((dir_path ("share") /= "doc") /= "<private>") /= "<project>"); + DIR (legal, dir_path ("doc")); + DIR (man, dir_path ("share") /= "man"); + DIR (man1, dir_path ("man") /= "man1"); #undef DIR @@ -312,22 +330,17 @@ namespace build2 // Enter module variables. // - auto& vp (rs.var_pool ()); + rs.var_pool ().insert<bool> ("for_install", variable_visibility::prereq); + + // The rest of the variables we enter are qualified so go straight + // for the public variable pool. + // + auto& vp (rs.var_pool (true /* public */)); // 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<path> ("install", variable_visibility::target); - vp.insert<bool> ("for_install", variable_visibility::prereq); - vp.insert<string> ("install.mode"); - vp.insert<bool> ("install.subdirs"); - } + vp.insert<string> ("install.mode"); + vp.insert<bool> ("install.subdirs"); // Environment. // @@ -372,23 +385,34 @@ namespace build2 const auto& gr (group_rule_); bs.insert_rule<alias> (perform_install_id, "install.alias", ar); - bs.insert_rule<alias> (perform_uninstall_id, "uninstall.alias", ar); + bs.insert_rule<alias> (perform_uninstall_id, "install.alias", ar); bs.insert_rule<fsdir> (perform_install_id, "install.fsdir", dr); bs.insert_rule<fsdir> (perform_uninstall_id, "install.fsdir", dr); bs.insert_rule<file> (perform_install_id, "install.file", fr); - bs.insert_rule<file> (perform_uninstall_id, "uninstall.file", fr); + bs.insert_rule<file> (perform_uninstall_id, "install.file", fr); - bs.insert_rule<target> (perform_install_id, "install.file", gr); - bs.insert_rule<target> (perform_uninstall_id, "uninstall.file", gr); + // Note: use mtime_target (instead of target) to take precedence over + // the fallback file rules below. + // + // @@ We could fix this by checking the target type in file_rule, + // similar to build2::file_rule. + // + bs.insert_rule<mtime_target> (perform_install_id, "install.group", gr); + bs.insert_rule<mtime_target> (perform_uninstall_id, "install.group", gr); - // Register the fallback file rule for the update-for-install + // Register the fallback file rule for the update-for-[un]install // operation, similar to update. // - rs.global_scope ().insert_rule<mtime_target> ( - perform_install_id, "install.file", file_rule::instance); - } + // @@ Hm, it's a bit fuzzy why we would be updating-for-install + // something outside of any project? + // + scope& gs (rs.global_scope ()); + + gs.insert_rule<mtime_target> (perform_install_id, "install.file", fr); + gs.insert_rule<mtime_target> (perform_uninstall_id, "install.file", fr); + } // Configuration. // @@ -401,7 +425,9 @@ namespace build2 using config::lookup_config; using config::specified_config; - bool s (specified_config (rs, "install")); + // Note: ignore config.install.{scope,manifest} (see below). + // + bool s (specified_config (rs, "install", {"scope", "manifest"})); // Adjust module priority so that the (numerous) config.install.* // values are saved at the end of config.build. @@ -409,6 +435,152 @@ namespace build2 if (s) config::save_module (rs, "install", INT32_MAX); + // 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 + // bundle -- bundle amalgamation + // strong -- strong amalgamation + // weak -- weak amalgamation + // global -- all projects (default) + // + // Note: can only be specified as a global override. + // + { + auto& v (vp.insert<string> ("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); + } + + // config.install.manifest + // + // Installation manifest. Valid values are a file path or `-` to dump + // the manifest to stdout. + // + // If specified during the install operation, then write the + // information about all the filesystem entries being installed into + // the manifest. If specified during uninstall, then remove the + // filesystem entries according to the manifest as opposed to the + // current build state. In particular, this functionality can be used + // to avoid surprising (and potentially lengthy) updates during + // uninstall that may happen because of changes to system-installed + // dependencies (for example, the compiler or standard library). + // + // @@ TODO: manifest uninstall is still TODO. + // + // Note: there is a single manifest per operation and thus this + // 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). 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. + // + // The format of the installation manifest is "JSON lines", that is, + // each line is a JSON text (this makes it possible to reverse the + // order of lines without loading the entire file into memory). For + // example (indented lines indicate line continuations): + // + // {"type":"directory","path":"/tmp/install","mode":"755"} + // {"type":"target","name":"/tmp/libhello/libs{hello}", + // "entries":[ + // {"type":"file","path":"/tmp/install/lib/libhello-1.0.so","mode":"755"}, + // {"type":"symlink","path":"/tmp/install/lib/libhello.so","target":"libhello-1.0.so"}]} + // + // Each line is a serialization of one of the following non-abstract + // C++ structs: + // + // struct entry // abstract + // { + // enum {directory, file, symlink, target} type; + // }; + // + // struct filesystem_entry: entry // abstract + // { + // path path; + // }; + // + // struct directory_entry: filesystem_entry + // { + // string mode; + // }; + // + // struct file_entry: filesystem_entry + // { + // string mode; + // }; + // + // struct symlink_entry: filesystem_entry + // { + // path target; + // }; + // + // struct target_entry: entry + // { + // string name; + // vector<filesystem_entry*> entries; + // }; + // + // New entry types may be added later. Additional entry members may be + // added later to existing entries after the existing members. + // + // 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")); + + // If specified, verify it is a global override. + // + if (lookup l = rs[v]) + { + if (!l.belongs (rs.global_scope ())) + fail << "config.install.manifest must be a global override" << + info << "specify !config.install.manifest=..."; + } + + config::unsave_variable (rs, v); + } + + // Support for relocatable install. + // + // Note that it is false by default since supporting relocatable + // installation may require extra effort and not all projects may + // support it. A project that is known not to support it should assert + // this fact in its root.build, for example: + // + // assert (!$install.relocatable) 'relocatable installation not supported' + // + { + auto& var (vp.insert<bool> ( "install.relocatable")); + auto& cvar (vp.insert<bool> ("config.install.relocatable")); + + value& v (rs.assign (var)); + + // Note: unlike other variables, for ease of assertion set it to + // false if no config.install.* is specified. + // + v = s && cast_false<bool> (lookup_config (rs, cvar, false)); + } + // Support for private install (aka poor man's Flatpack). // const dir_path* p; @@ -446,35 +618,109 @@ namespace build2 } } - // Global config.install.* values. + // config.install.filter // - 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"); + // Installation filterting. The value of this variable is a list of + // key-value pairs that specify the filesystem entries to include or + // exclude from the installation. For example, the following filters + // will omit installing headers and static libraries (notice the + // quoting of the wildcard). + // + // config.install.filter='include/@false "*.a"@false' + // + // The key in each pair is a file or directory path or a path wildcard + // pattern. If a key is relative and contains a directory component or + // is a directory, then it is treated relative to the corresponding + // config.install.* location. Otherwise (simple path, normally a + // pattern), it is matched against the leaf of any path. Note that if + // an absolute path is specified, it should be without the + // config.install.chroot prefix. + // + // The value in each pair is either true (include) or false (exclude). + // The filters are evaluated in the order specified and the first + // match that is found determines the outcome. If no match is found, + // the default is to include. For a directory, while false means + // exclude all the sub-paths inside this directory, true does not mean + // that all the sub-paths will be included wholesale. Rather, the + // matched component of the sub-path is treated as included with the + // rest of the components matched against the following + // sub-filters. For example: + // + // config.install.filter=' + // include/x86_64-linux-gnu/@true + // include/x86_64-linux-gnu/details/@false + // include/@false' + // + // The true or false value may be followed by comma and the `symlink` + // modifier to only apply to symlink filesystem entries. For example: + // + // config.install.filter='"*.so"@false,symlink' + // + // Note that this mechanism only affects what gets physically copied + // to the installation directory without affecting what gets built for + // install or the view of what gets installed at the buildfile level. + // For example, given the `include/@false *.a@false` filters, static + // libraries will still be built (unless arranged not to with + // config.bin.lib) and the pkg-config files will still end up with -I + // options pointing to the header installation directory. Note also + // that this mechanism applies to both install and uninstall + // operations. + // + // If you are familiar with the Debian or Fedora packaging, this + // mechanism is somewhat similar to (and can be used for a similar + // purpose as) the Debian's .install files and Fedora's %files spec + // file sections that are used to split the installation into multiple + // binary packages. + // + { + auto& var (vp.insert<filters> ( "install.filter")); + auto& cvar (vp.insert<filters> ("config.install.filter")); - 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"); + value& v (rs.assign (var)); - 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); + if (s) + { + if (lookup l = lookup_config (rs, cvar, nullptr)) + v = cast<filters> (l); + } + } - 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); + // 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, "include_arch", dir_include_arch); + set_dir (s, p, rs, "share", dir_share); + set_dir (s, p, rs, "data", dir_data); + set_dir (s, p, rs, "buildfile", dir_buildfile); + + 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. // + // Note that for exe{} we also set explicit 755 mode in case it gets + // installed somewhere else where the default is not 755 (for example to + // libexec/, which on Debian has the 644 mode). + // install_path<exe> (bs, dir_path ("bin")); + install_mode<exe> (bs, "755"); install_path<doc> (bs, dir_path ("doc")); install_path<legal> (bs, dir_path ("legal")); install_path<man> (bs, dir_path ("man")); |