diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2023-04-04 14:10:27 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2023-04-04 14:10:27 +0200 |
commit | 8b9701d2ad76a9a571c445b318557261a4922758 (patch) | |
tree | 1de11ba8c7e1d465f4a95005d7115774696a2a87 | |
parent | 7b06ee81ab0e8a2199c4dce07ec67282c4f52f62 (diff) |
Remove global override restriction from config.install.filter
-rw-r--r-- | doc/manual.cli | 11 | ||||
-rw-r--r-- | libbuild2/install/functions.cxx | 16 | ||||
-rw-r--r-- | libbuild2/install/init.cxx | 167 | ||||
-rw-r--r-- | libbuild2/install/operation.cxx | 285 | ||||
-rw-r--r-- | libbuild2/install/operation.hxx | 18 | ||||
-rw-r--r-- | libbuild2/install/rule.cxx | 14 | ||||
-rw-r--r-- | libbuild2/install/utility.cxx | 245 | ||||
-rw-r--r-- | libbuild2/install/utility.hxx | 13 |
8 files changed, 360 insertions, 409 deletions
diff --git a/doc/manual.cli b/doc/manual.cli index e9b59d2..101493c 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -6287,8 +6287,7 @@ assert (!$install.relocatable) 'relocatable installation not supported' While project authors determine what gets installed at the \c{buildfile} level, the users of the project can further filter the installation using the -\c{config.install.filter} variable. \N{This variable can only be specified as -a global override.} +\c{config.install.filter} variable. 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, @@ -6296,7 +6295,7 @@ the following filters will omit installing headers and static libraries (notice the quoting of the wildcard). \ -$ b install !config.install.filter='include/@false \"*.a\"@false' +$ b install config.install.filter='include/@false \"*.a\"@false' \ The key in each pair is a file or directory path or a path wildcard pattern. @@ -6316,7 +6315,7 @@ as included with the rest of the components matched against the following sub-filters. For example: \ -$ b install !config.install.filter=' +$ b install config.install.filter=' include/x86_64-linux-gnu/@true include/x86_64-linux-gnu/details/@false include/@false' @@ -6326,7 +6325,7 @@ The \c{true} or \c{false} value may be followed by comma and the \c{symlink} modifier to only apply to symlink filesystem entries. For example: \ -$ b !config.install.filter='\"*.so\"@false,symlink' +$ b config.install.filter='\"*.so\"@false,symlink' \ Note that this mechanism only affects what gets physically copied to the @@ -6349,7 +6348,7 @@ and shared library symlinks; assuming the platform uses the \c{.a}/\c{.so} extensions for the libraries): \ -$ b install !config.install.filter=' +$ b install config.install.filter=' include/@false pkgconfig/@false \"lib/*.a\"@false diff --git a/libbuild2/install/functions.cxx b/libbuild2/install/functions.cxx index d62b578..1de4d3e 100644 --- a/libbuild2/install/functions.cxx +++ b/libbuild2/install/functions.cxx @@ -5,7 +5,6 @@ #include <libbuild2/variable.hxx> #include <libbuild2/install/utility.hxx> -#include <libbuild2/install/operation.hxx> namespace build2 { @@ -98,8 +97,7 @@ namespace build2 // whether path is syntactially a directory (ends with a directory // separator). // - // Note that this function is not pure and can only be called from a - // install or uninstall operation recipe. + // Note that this function is not pure. // f.insert (".filter", false) += [] (const scope* s, path p, @@ -108,16 +106,6 @@ namespace build2 if (s == nullptr) fail << "install.filter() called out of scope" << endf; - context& ctx (s->ctx); - - if (ctx.phase != run_phase::match && - ctx.phase != run_phase::execute) - fail << "install.filter() can only be called from recipe"; - - if (ctx.current_inner_oif != &op_install && - ctx.current_inner_oif != &op_uninstall) - fail << "install.filter() can only be called during install/uninstall"; - entry_type t; if (ot) { @@ -145,7 +133,7 @@ namespace build2 p.make_leaf (); } - return context_data::filter (*s->root_scope (), d, p, t); + return filter_entry (*s->root_scope (), d, p, t); }; } } diff --git a/libbuild2/install/init.cxx b/libbuild2/install/init.cxx index 69f578b..dfd78df 100644 --- a/libbuild2/install/init.cxx +++ b/libbuild2/install/init.cxx @@ -421,10 +421,9 @@ namespace build2 using config::lookup_config; using config::specified_config; - // Note: ignore config.install.{scope,filter,manifest} (see below). + // Note: ignore config.install.{scope,manifest} (see below). // - bool s (specified_config ( - rs, "install", {"scope", "filter", "manifest"})); + 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. @@ -461,77 +460,6 @@ namespace build2 config::unsave_variable (rs, v); } - // config.install.filter - // - // 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. - // - // Note: can only be specified as a global override. - // - { - auto& v (vp.insert<filters> ("config.install.filter")); - - // If specified, verify it is a global override. - // - if (lookup l = rs[v]) - { - if (!l.belongs (rs.global_scope ())) - fail << "config.install.filter must be a global override" << - info << "specify !config.install.filter=..."; - } - - config::unsave_variable (rs, v); - } - // config.install.manifest // // Installation manifest. Valid values are a file path or `-` to dump @@ -628,6 +556,27 @@ namespace build2 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; @@ -665,25 +614,71 @@ namespace build2 } } - // Support for relocatable install. + // config.install.filter // - // 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: + // 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). // - // assert (!$install.relocatable) 'relocatable installation not supported' + // 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<bool> ( "install.relocatable")); - auto& cvar (vp.insert<bool> ("config.install.relocatable")); + auto& var (vp.insert<filters> ( "install.filter")); + auto& cvar (vp.insert<filters> ("config.install.filter")); 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)); + if (s) + { + if (lookup l = lookup_config (rs, cvar, nullptr)) + v = cast<filters> (l); + } } // Global config.install.* values. diff --git a/libbuild2/install/operation.cxx b/libbuild2/install/operation.cxx index 1b63bc0..ce5d24a 100644 --- a/libbuild2/install/operation.cxx +++ b/libbuild2/install/operation.cxx @@ -19,255 +19,10 @@ namespace build2 { namespace install { - bool context_data:: - filter (const scope& rs, - const dir_path& base, - const path& leaf, - entry_type type) - { - assert (type != entry_type::unknown && - (type == entry_type::directory) == leaf.empty ()); - - context& ctx (rs.ctx); - - auto& d (*static_cast<context_data*> (ctx.current_inner_odata.get ())); - - if (d.filters == nullptr || d.filters->empty ()) - return true; - - tracer trace ("install::context_data::filter"); - - // Parse, resolve, and apply each filter in order. - // - // If redoing all this work for every entry proves too slow, we can - // consider some form of caching (e.g., on the per-project basis). - // - size_t limit (0); // See below. - - for (const pair<string, string>& kv: *d.filters) - { - path k; - try - { - k = path (kv.first); - - if (k.absolute ()) - k.normalize (); - } - catch (const invalid_path&) - { - fail << "invalid path '" << kv.first << "' in config.install.filter " - << "value"; - } - - bool v; - { - const string& s (kv.second); - - size_t p (s.find (',')); - - if (s.compare (0, p, "true") == 0) - v = true; - else if (s.compare (0, p, "false") == 0) - v = false; - else - fail << "expected true or false instead of '" << string (s, 0, p) - << "' in config.install.filter value"; - - if (p != string::npos) - { - if (s.compare (p + 1, string::npos, "symlink") == 0) - { - if (type != entry_type::symlink) - continue; - } - else - fail << "unknown modifier '" << string (s, p + 1) << "' in " - << "config.install.filter value"; - } - } - - // @@ TODO (see below for all the corner cases). Note that in a sense - // we already have the match file in any subdirectory support via - // simple patterns so perhaps this is not worth the trouble. Or we - // could support some limited form (e.g., `**` should be in the - // last component). But it may still be tricky to determine if - // it is a sub-filter. - // - if (path_pattern_recursive (k)) - fail << "recursive wildcard pattern '" << kv.first << "' in " - << "config.install.filter value"; - - if (k.simple () && !k.to_directory ()) - { - // Simple name/pattern matched against the leaf. - // - // @@ What if it is `**`? - // - if (path_pattern (k)) - { - if (!path_match (leaf, k)) - continue; - } - else - { - if (k != leaf) - continue; - } - } - else - { - // Split into directory and leaf. - // - // @@ What if leaf is `**`? - // - dir_path d; - if (k.to_directory ()) - { - d = path_cast<dir_path> (move (k)); - k = path (); // No leaf. - } - else - { - d = k.directory (); - k.make_leaf (); - } - - // Resolve relative directory. - // - // Note that this resolution is potentially project-specific (that - // is, different projects may have different install.* locaitons). - // - // Note that if the first component is/contains a wildcard (e.g., - // `*/`), then the resulution will fail, which feels correct (what - // does */ mean?). - // - if (d.relative ()) - { - // @@ Strictly speaking, this should be base, not root scope. - // - d = resolve_dir (rs, move (d)); - } - - // Return the number of path components in the path. - // - auto path_comp = [] (const path& p) - { - size_t n (0); - for (auto i (p.begin ()); i != p.end (); ++i) - ++n; - return n; - }; - - // We need the sub() semantics but which uses pattern match instead - // of equality for the prefix. Looks like chopping off the path and - // calling path_match() on that is the best we can do. - // - // @@ Assumes no `**` components. - // - auto path_sub = [&path_comp] (const dir_path& ent, - const dir_path& pat, - size_t n = 0) - { - if (n == 0) - n = path_comp (pat); - - dir_path p; - for (auto i (ent.begin ()); n != 0 && i != ent.end (); --n, ++i) - p.combine (*i, i.separator ()); - - return path_match (p, pat); - }; - - // The following checks should continue on no match and fall through - // to return. - // - if (k.empty ()) // Directory. - { - // Directories have special semantics. - // - // Consider this sequence of filters: - // - // include/x86_64-linux-gnu/@true - // include/x86_64-linux-gnu/details/@false - // include/@false - // - // It seems the semantics we want is that only subcomponent - // filters should apply. Maybe remember the latest matched - // directory as a current limit? But perhaps we don't need to - // remember the directory itself but the number of path - // components? - // - // I guess for patterns we will use the actual matched directory, - // not the pattern, to calculate the limit? @@ Because we - // currently don't support `**`, we for now can count components - // in the pattern. - - // Check if this is a sub-filter. - // - size_t n (path_comp (d)); - if (n <= limit) - continue; - - if (path_pattern (d)) - { - if (!path_sub (base, d, n)) - continue; - } - else - { - if (!base.sub (d)) - continue; - } - - if (v) - { - limit = n; - continue; // Continue looking for sub-filters. - } - } - else - { - if (path_pattern (d)) - { - if (!path_sub (base, d)) - continue; - } - else - { - if (!base.sub (d)) - continue; - } - - if (path_pattern (k)) - { - // @@ Does not handle `**`. - // - if (!path_match (leaf, k)) - continue; - } - else - { - if (k != leaf) - continue; - } - } - } - - l4 ([&]{trace << (base / leaf) - << (v ? " included by " : " excluded by ") - << kv.first << '@' << kv.second;}); - return v; - } - - return true; - } - #ifndef BUILD2_BOOTSTRAP context_data:: - context_data (const install::filters* fs, const path* mf) - : filters (fs), - manifest_name (mf), + context_data (const path* mf) + : manifest_name (mf), manifest_os (mf != nullptr ? open_file_or_stdout (manifest_name, manifest_ofs) : manifest_ofs), @@ -513,8 +268,7 @@ namespace build2 } #else context_data:: - context_data (const install::filters* fs, const path*) - : filters (fs) + context_data (const path*) { } @@ -583,14 +337,10 @@ namespace build2 if (inner) { - // See if we need to filter and/or write the installation manifest. + // See if we need to write the installation manifest. // // Note: go straight for the public variable pool. // - const filters* fs ( - cast_null<filters> ( - ctx.global_scope[*ctx.var_pool.find ("config.install.filter")])); - const path* mf ( cast_null<path> ( ctx.global_scope[*ctx.var_pool.find ("config.install.manifest")])); @@ -600,30 +350,7 @@ namespace build2 // value of config.install.root. ctx.current_inner_odata = context::current_data_ptr ( - new context_data (fs, mf), - [] (void* p) {delete static_cast<context_data*> (p);}); - } - } - - static void - uninstall_pre (context& ctx, - const values& params, - bool inner, - const location& l) - { - // Note: a subset of install_pre(). - // - if (!params.empty ()) - fail (l) << "unexpected parameters for operation uninstall"; - - if (inner) - { - const filters* fs ( - cast_null<filters> ( - ctx.global_scope[*ctx.var_pool.find ("config.install.filter")])); - - ctx.current_inner_odata = context::current_data_ptr ( - new context_data (fs, nullptr), + new context_data (mf), [] (void* p) {delete static_cast<context_data*> (p);}); } } @@ -682,7 +409,7 @@ namespace build2 0 /* concurrency */, // Run serially &pre_uninstall, nullptr, - &uninstall_pre, + nullptr, nullptr, nullptr, nullptr diff --git a/libbuild2/install/operation.hxx b/libbuild2/install/operation.hxx index d71d8f3..bd818b4 100644 --- a/libbuild2/install/operation.hxx +++ b/libbuild2/install/operation.hxx @@ -12,7 +12,7 @@ #include <libbuild2/utility.hxx> #include <libbuild2/operation.hxx> -#include <libbuild2/filesystem.hxx> // auto_rmfile, entry_type +#include <libbuild2/filesystem.hxx> // auto_rmfile namespace build2 { @@ -22,25 +22,11 @@ namespace build2 extern const operation_info op_uninstall; extern const operation_info op_update_for_install; - using filters = vector<pair<string, string>>; - // Set as context::current_inner_odata during the install/uninstall inner // operations. // struct context_data { - // Filters. - // - const install::filters* filters; - - // If entry type is a directory, then leaf must be empty. - // - static bool - filter (const scope& rs, - const dir_path& base, - const path& leaf, - entry_type); - // Manifest. // #ifndef BUILD2_BOOTSTRAP @@ -92,7 +78,7 @@ namespace build2 // Constructor. // explicit - context_data (const install::filters*, const path* manifest); + context_data (const path* manifest); }; } } diff --git a/libbuild2/install/rule.cxx b/libbuild2/install/rule.cxx index f8e3e05..c0fbe0c 100644 --- a/libbuild2/install/rule.cxx +++ b/libbuild2/install/rule.cxx @@ -807,8 +807,7 @@ namespace build2 // Note that this also means we won't have the directory entries in the // manifest created with dry-run. Probably not a big deal. // - if (ctx.dry_run || - !context_data::filter (rs, d, path (), entry_type::directory)) + if (ctx.dry_run || !filter_entry (rs, d, path (), entry_type::directory)) return; dir_path chd (chroot_path (rs, d)); @@ -890,7 +889,7 @@ namespace build2 const path& leaf (name.empty () ? f.leaf () : name); - if (!context_data::filter (rs, base.dir, leaf, entry_type::regular)) + if (!filter_entry (rs, base.dir, leaf, entry_type::regular)) return; path relf (relative (f)); @@ -959,7 +958,7 @@ namespace build2 context& ctx (rs.ctx); - if (!context_data::filter (rs, base.dir, link, entry_type::symlink)) + if (!filter_entry (rs, base.dir, link, entry_type::symlink)) return; if (link_target.absolute () && @@ -1173,8 +1172,7 @@ namespace build2 // See install_d() for the rationale. // - if (ctx.dry_run || - !context_data::filter (rs, d, path (), entry_type::directory)) + if (ctx.dry_run || !filter_entry (rs, d, path (), entry_type::directory)) return false; dir_path chd (chroot_path (rs, d)); @@ -1356,7 +1354,7 @@ namespace build2 const path& leaf (name.empty () ? t->path ().leaf () : name); - if (!context_data::filter (rs, base.dir, leaf, entry_type::regular)) + if (!filter_entry (rs, base.dir, leaf, entry_type::regular)) return false; dir_path chd (chroot_path (rs, base.dir)); @@ -1401,7 +1399,7 @@ namespace build2 { assert (link.simple () && !link.empty ()); - if (!context_data::filter (rs, base.dir, link, entry_type::symlink)) + if (!filter_entry (rs, base.dir, link, entry_type::symlink)) return false; dir_path chd (chroot_path (rs, base.dir)); diff --git a/libbuild2/install/utility.cxx b/libbuild2/install/utility.cxx index c2a581e..c8e1699 100644 --- a/libbuild2/install/utility.cxx +++ b/libbuild2/install/utility.cxx @@ -3,6 +3,9 @@ #include <libbuild2/install/utility.hxx> +#include <libbuild2/variable.hxx> +#include <libbuild2/diagnostics.hxx> + namespace build2 { namespace install @@ -32,5 +35,247 @@ namespace build2 return nullptr; } + + bool + filter_entry (const scope& rs, + const dir_path& base, + const path& leaf, + entry_type type) + { + assert (type != entry_type::unknown && + (type == entry_type::directory) == leaf.empty ()); + + const filters* fs (cast_null<filters> (rs["install.filter"])); + + if (fs == nullptr || fs->empty ()) + return true; + + tracer trace ("install::filter"); + + // Parse, resolve, and apply each filter in order. + // + // If redoing all this work for every entry proves too slow, we can + // consider some form of caching (e.g., on the per-project basis). + // + size_t limit (0); // See below. + + for (const pair<string, string>& kv: *fs) + { + path k; + try + { + k = path (kv.first); + + if (k.absolute ()) + k.normalize (); + } + catch (const invalid_path&) + { + fail << "invalid path '" << kv.first << "' in config.install.filter " + << "value"; + } + + bool v; + { + const string& s (kv.second); + + size_t p (s.find (',')); + + if (s.compare (0, p, "true") == 0) + v = true; + else if (s.compare (0, p, "false") == 0) + v = false; + else + fail << "expected true or false instead of '" << string (s, 0, p) + << "' in config.install.filter value"; + + if (p != string::npos) + { + if (s.compare (p + 1, string::npos, "symlink") == 0) + { + if (type != entry_type::symlink) + continue; + } + else + fail << "unknown modifier '" << string (s, p + 1) << "' in " + << "config.install.filter value"; + } + } + + // @@ TODO (see below for all the corner cases). Note that in a sense + // we already have the match file in any subdirectory support via + // simple patterns so perhaps this is not worth the trouble. Or we + // could support some limited form (e.g., `**` should be in the + // last component). But it may still be tricky to determine if + // it is a sub-filter. + // + if (path_pattern_recursive (k)) + fail << "recursive wildcard pattern '" << kv.first << "' in " + << "config.install.filter value"; + + if (k.simple () && !k.to_directory ()) + { + // Simple name/pattern matched against the leaf. + // + // @@ What if it is `**`? + // + if (path_pattern (k)) + { + if (!path_match (leaf, k)) + continue; + } + else + { + if (k != leaf) + continue; + } + } + else + { + // Split into directory and leaf. + // + // @@ What if leaf is `**`? + // + dir_path d; + if (k.to_directory ()) + { + d = path_cast<dir_path> (move (k)); + k = path (); // No leaf. + } + else + { + d = k.directory (); + k.make_leaf (); + } + + // Resolve relative directory. + // + // Note that this resolution is potentially project-specific (that + // is, different projects may have different install.* locaitons). + // + // Note that if the first component is/contains a wildcard (e.g., + // `*/`), then the resulution will fail, which feels correct (what + // does */ mean?). + // + if (d.relative ()) + { + // @@ Strictly speaking, this should be base, not root scope. + // + d = resolve_dir (rs, move (d)); + } + + // Return the number of path components in the path. + // + auto path_comp = [] (const path& p) + { + size_t n (0); + for (auto i (p.begin ()); i != p.end (); ++i) + ++n; + return n; + }; + + // We need the sub() semantics but which uses pattern match instead + // of equality for the prefix. Looks like chopping off the path and + // calling path_match() on that is the best we can do. + // + // @@ Assumes no `**` components. + // + auto path_sub = [&path_comp] (const dir_path& ent, + const dir_path& pat, + size_t n = 0) + { + if (n == 0) + n = path_comp (pat); + + dir_path p; + for (auto i (ent.begin ()); n != 0 && i != ent.end (); --n, ++i) + p.combine (*i, i.separator ()); + + return path_match (p, pat); + }; + + // The following checks should continue on no match and fall through + // to return. + // + if (k.empty ()) // Directory. + { + // Directories have special semantics. + // + // Consider this sequence of filters: + // + // include/x86_64-linux-gnu/@true + // include/x86_64-linux-gnu/details/@false + // include/@false + // + // It seems the semantics we want is that only subcomponent + // filters should apply. Maybe remember the latest matched + // directory as a current limit? But perhaps we don't need to + // remember the directory itself but the number of path + // components? + // + // I guess for patterns we will use the actual matched directory, + // not the pattern, to calculate the limit? @@ Because we + // currently don't support `**`, we for now can count components + // in the pattern. + + // Check if this is a sub-filter. + // + size_t n (path_comp (d)); + if (n <= limit) + continue; + + if (path_pattern (d)) + { + if (!path_sub (base, d, n)) + continue; + } + else + { + if (!base.sub (d)) + continue; + } + + if (v) + { + limit = n; + continue; // Continue looking for sub-filters. + } + } + else + { + if (path_pattern (d)) + { + if (!path_sub (base, d)) + continue; + } + else + { + if (!base.sub (d)) + continue; + } + + if (path_pattern (k)) + { + // @@ Does not handle `**`. + // + if (!path_match (leaf, k)) + continue; + } + else + { + if (k != leaf) + continue; + } + } + } + + l4 ([&]{trace << (base / leaf) + << (v ? " included by " : " excluded by ") + << kv.first << '@' << kv.second;}); + return v; + } + + return true; + } } } diff --git a/libbuild2/install/utility.hxx b/libbuild2/install/utility.hxx index 2ba7b18..dca8eb4 100644 --- a/libbuild2/install/utility.hxx +++ b/libbuild2/install/utility.hxx @@ -9,6 +9,7 @@ #include <libbuild2/scope.hxx> #include <libbuild2/target.hxx> +#include <libbuild2/filesystem.hxx> // entry_type #include <libbuild2/export.hxx> @@ -107,6 +108,18 @@ namespace build2 const dir_path* d (cast_null<dir_path> (rs["install.chroot"])); return d != nullptr ? *d / p.leaf (p.root_directory ()) : p; } + + // Installation filtering (config.install.filter). + // + // If entry type is a directory, then leaf must be empty. + // + using filters = vector<pair<string, string>>; + + LIBBUILD2_SYMEXPORT bool + filter_entry (const scope& rs, + const dir_path& base, + const path& leaf, + entry_type); } } |