aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2023-04-04 14:10:27 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2023-04-04 14:10:27 +0200
commit8b9701d2ad76a9a571c445b318557261a4922758 (patch)
tree1de11ba8c7e1d465f4a95005d7115774696a2a87
parent7b06ee81ab0e8a2199c4dce07ec67282c4f52f62 (diff)
Remove global override restriction from config.install.filter
-rw-r--r--doc/manual.cli11
-rw-r--r--libbuild2/install/functions.cxx16
-rw-r--r--libbuild2/install/init.cxx167
-rw-r--r--libbuild2/install/operation.cxx285
-rw-r--r--libbuild2/install/operation.hxx18
-rw-r--r--libbuild2/install/rule.cxx14
-rw-r--r--libbuild2/install/utility.cxx245
-rw-r--r--libbuild2/install/utility.hxx13
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);
}
}