From 03c931e54e618221b69cfcd3dfb462e50ecad780 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 28 Oct 2022 23:21:29 +0300 Subject: Add support for package build configurations --- mod/build-target-config.cxx | 258 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 mod/build-target-config.cxx (limited to 'mod/build-target-config.cxx') diff --git a/mod/build-target-config.cxx b/mod/build-target-config.cxx new file mode 100644 index 0000000..a30cf07 --- /dev/null +++ b/mod/build-target-config.cxx @@ -0,0 +1,258 @@ +// file : mod/target-build-config.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#include + +#include // alpha(), etc. +#include + +namespace brep +{ + using namespace std; + using namespace butl; + using namespace bpkg; + + // The default underlying class set expressions (see below). + // + static const build_class_expr default_ucs_expr ( + {"default"}, '+', "Default."); + + static const build_class_expr all_ucs_expr ( + {"all"}, '+', "All."); + + bool + exclude (const build_package_config& pc, + const build_class_exprs& cbs, + const build_constraints& ccs, + const build_target_config& tc, + const map& class_inheritance_map, + string* reason, + bool default_all_ucs) + { + const build_class_exprs& exprs (pc.effective_builds (cbs)); + const build_constraints& constrs (pc.effective_constraints (ccs)); + + // Save the first sentence of the reason, lower-case the first letter if + // the beginning looks like a word (all subsequent characters until a + // whitespace are lower-case letters). + // + auto sanitize = [] (const string& reason) + { + string r (reason.substr (0, reason.find ('.'))); + + char c (r[0]); // Can be '\0'. + if (alpha (c) && c == ucase (c)) + { + bool word (true); + + for (size_t i (1); + i != r.size () && (c = r[i]) != ' ' && c != '\t' && c != '\n'; + ++i) + { + // Is not a word if contains a non-letter or an upper-case letter. + // + if (!alpha (c) || c == ucase (c)) + { + word = false; + break; + } + } + + if (word) + r[0] = lcase (r[0]); + } + + return r; + }; + + // First, match the configuration against the package underlying build + // class set and expressions. + // + bool m (false); + + // Match the configuration against an expression, updating the match + // result. + // + // We will use a comment of the first encountered excluding expression + // (changing the result from true to false) or non-including one (leaving + // the false result) as an exclusion reason. + // + auto match = [&tc, &m, reason, &sanitize, &class_inheritance_map] + (const build_class_expr& e) + { + bool pm (m); + e.match (tc.classes, class_inheritance_map, m); + + if (reason != nullptr) + { + // Reset the reason which, if saved, makes no sense anymore. + // + if (m) + { + reason->clear (); + } + else if (reason->empty () && + // + // Exclusion. + // + (pm || + // + // Non-inclusion. Make sure that the build class expression + // is empty or starts with an addition (+...). + // + e.expr.empty () || + e.expr.front ().operation == '+')) + { + *reason = sanitize (e.comment); + } + } + }; + + // Determine the underlying class set. Note that in the future we can + // potentially extend the underlying set with special classes. + // + const build_class_expr* ucs ( + !exprs.empty () && !exprs.front ().underlying_classes.empty () + ? &exprs.front () + : nullptr); + + // Note that the combined package build configuration class expression can + // be represented as the underlying class set used as a starting set for + // the original expressions and a restricting set, simultaneously. For + // example, for the expression: + // + // default legacy : -msvc + // + // the resulting expression will be: + // + // +( +default +legacy ) -msvc &( +default +legacy ) + // + // Let's, however, optimize it a bit based on the following facts: + // + // - If the underlying class set expression (+default +legacy in the above + // example) evaluates to false, then the resulting expression also + // evaluates to false due to the trailing '&' operation. Thus, we don't + // need to evaluate further if that's the case. + // + // - On the other hand, if the underlying class set expression evaluates + // to true, then we don't need to apply the trailing '&' operation as it + // cannot affect the result. + // + const build_class_expr& ucs_expr ( + ucs != nullptr ? build_class_expr (ucs->underlying_classes, + '+', + ucs->comment) : + default_all_ucs ? all_ucs_expr : + default_ucs_expr); + + match (ucs_expr); + + if (m) + { + for (const build_class_expr& e: exprs) + match (e); + } + + // Exclude the configuration if it doesn't match the compound expression. + // + if (!m) + return true; + + // Now check if the configuration is excluded/included via the patterns. + // + // To implement matching of absent name components with wildcard-only + // pattern components we are going to convert names to paths (see + // dash_components_to_path() for details). + // + // And if any of the build-{include,exclude} values (which is legal) or + // the build configuration name/target (illegal) are invalid paths, then + // we assume no match. + // + if (!constrs.empty ()) + try + { + path cn (dash_components_to_path (tc.name)); + path tg (dash_components_to_path (tc.target.string ())); + + for (const build_constraint& c: constrs) + { + if (path_match (cn, + dash_components_to_path (c.config), + dir_path () /* start */, + path_match_flags::match_absent) && + (!c.target || + path_match (tg, + dash_components_to_path (*c.target), + dir_path () /* start */, + path_match_flags::match_absent))) + { + if (!c.exclusion) + return false; + + if (reason != nullptr) + *reason = sanitize (c.comment); + + return true; + } + } + } + catch (const invalid_path&) {} + + return false; + } + + path + dash_components_to_path (const string& pattern) + { + string r; + size_t nstar (0); + for (const path_pattern_term& pt: path_pattern_iterator (pattern)) + { + switch (pt.type) + { + case path_pattern_term_type::star: + { + // Replace ** with */**/* and skip all the remaining stars that may + // follow in this sequence. + // + if (nstar == 0) + r += "*"; + else if (nstar == 1) + r += "/**/*"; // The first star is already copied. + + break; + } + case path_pattern_term_type::literal: + { + // Replace '-' with '/' and fall through otherwise. + // + if (get_literal (pt) == '-') + { + r += '/'; + break; + } + } + // Fall through. + default: + { + r.append (pt.begin, pt.end); // Copy the pattern term as is. + } + } + + nstar = pt.star () ? nstar + 1 : 0; + } + + // Append the trailing slash to match the resulting paths as directories. + // This is required for the trailing /* we could append to match absent + // directory path components (see path_match_flags::match_absent for + // details). + // + // Note that valid dash components may not contain a trailing dash. + // Anyway, any extra trailing slashes will be ignored by the path + // constructor. + // + r += '/'; + + return path (move (r)); + } +} -- cgit v1.1