// file : libbuild2/install/utility.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include #include namespace build2 { namespace install { const scope* install_scope (const target& t) { context& ctx (t.ctx); // Note: go straight for the public variable pool. // const variable& var (*ctx.var_pool.find ("config.install.scope")); if (const string* s = cast_null (ctx.global_scope[var])) { if (*s == "project") return &t.root_scope (); else if (*s == "bundle") return &t.bundle_scope (); else if (*s == "strong") return &t.strong_scope (); else if (*s == "weak") return &t.weak_scope (); else if (*s != "global") fail << "invalid " << var << " value '" << *s << "'"; } 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 (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). // auto i (fs->begin ()); bool negate (false); if (i->first == "!") { negate = true; ++i; } size_t limit (0); // See below. for (auto e (fs->end ()); i != e; ++i) { const pair>& kv (*i); 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 ? *kv.second : string ()); 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" << endf; 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 (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; } } } if (negate) v = !v; l4 ([&]{trace << (base / leaf) << (v ? " included by " : " excluded by ") << kv.first << '@' << *kv.second;}); return v; } return !negate; } } }