diff options
Diffstat (limited to 'libbuild2/target.cxx')
-rw-r--r-- | libbuild2/target.cxx | 986 |
1 files changed, 785 insertions, 201 deletions
diff --git a/libbuild2/target.cxx b/libbuild2/target.cxx index df03128..2a134a4 100644 --- a/libbuild2/target.cxx +++ b/libbuild2/target.cxx @@ -22,26 +22,13 @@ namespace build2 bool target_type:: is_a (const char* n) const { - if (strcmp (name, n) == 0) - return true; - - for (const target_type* b (base); b != nullptr; b = b->base) + for (const target_type* b (this); b != nullptr; b = b->base) if (strcmp (b->name, n) == 0) return true; return false; } - bool target_type:: - is_a_base (const target_type& tt) const - { - for (const target_type* b (base); b != nullptr; b = b->base) - if (*b == tt) - return true; - - return false; - } - // target_key // void target_key:: @@ -51,7 +38,9 @@ namespace build2 if (!name->empty ()) { v = *name; - target::combine_name (v, ext, false /* @@ TODO: what to do? */); + // @@ TMP: see also other calls to combine_name() -- need to fix. + // + target::combine_name (v, ext, false /* @@ TMP: what to do? */); } else assert (!ext || ext->empty ()); // Unspecified or none. @@ -69,6 +58,7 @@ namespace build2 // static const char* const target_state_[] = { + "<invalid>", // Absent/invalid (see target_state for details). "unknown", "unchanged", "postponed", @@ -78,10 +68,10 @@ namespace build2 "group" }; - ostream& - operator<< (ostream& os, target_state ts) + string + to_string (target_state ts) { - return os << target_state_[static_cast<uint8_t> (ts)]; + return target_state_[static_cast<uint8_t> (ts)]; } // target @@ -91,7 +81,6 @@ namespace build2 target:: ~target () { - clear_data (); } const string& target:: @@ -122,31 +111,40 @@ namespace build2 group_view target:: group_members (action) const { - assert (false); // Not a group or doesn't expose its members. + // Not a group or doesn't expose its members. + // return group_view {nullptr, 0}; } const scope& target:: - base_scope () const + base_scope_impl () const { // If this target is from the src tree, use its out directory to find // the scope. // - return ctx.scopes.find (out_dir ()); - } + const scope& s (ctx.scopes.find_out (out_dir ())); - const scope& target:: - root_scope () const - { - // This is tricky to cache so we do the lookup for now. + // Cache unless we are in the load phase. // - const scope* r (base_scope ().root_scope ()); - assert (r != nullptr); - return *r; + if (ctx.phase != run_phase::load) + { + const scope* e (nullptr); + if (!base_scope_.compare_exchange_strong ( + e, + &s, + memory_order_release, + memory_order_consume)) + assert (e == &s); + } + + return s; } pair<lookup, size_t> target:: - lookup_original (const variable& var, bool target_only) const + lookup_original (const variable& var, + bool target_only, + const scope* bs, + bool locked) const { pair<lookup_type, size_t> r (lookup_type (), 0); @@ -157,37 +155,72 @@ namespace build2 r.first = lookup_type (*p.first, p.second, vars); } - const target* g (nullptr); + const target* g1 (nullptr); + const target* g2 (nullptr); if (!r.first) { ++r.second; + // While we went back to not treating the first member as a group for + // variable lookup, let's keep this logic in case one day we end up with + // a separate ad hoc group target. + // +#if 0 + // In case of an ad hoc group, we may have to look in two groups. + // + if ((g1 = group) != nullptr) + { + auto p (g1->vars.lookup (var)); + if (p.first != nullptr) + r.first = lookup_type (*p.first, p.second, g1->vars); + else + { + if ((g2 = g1->group) != nullptr) + { + auto p (g2->vars.lookup (var)); + if (p.first != nullptr) + r.first = lookup_type (*p.first, p.second, g2->vars); + } + } + } +#else // Skip looking up in the ad hoc group, which is semantically the // first/primary member. // - if ((g = group == nullptr + if ((g1 = group == nullptr ? nullptr : group->adhoc_group () ? group->group : group)) { - auto p (g->vars.lookup (var)); + auto p (g1->vars.lookup (var)); if (p.first != nullptr) - r.first = lookup_type (*p.first, p.second, g->vars); + r.first = lookup_type (*p.first, p.second, g1->vars); } +#endif } - // Delegate to scope's find_original(). + // Delegate to scope's lookup_original(). // if (!r.first) { if (!target_only) { - auto p (base_scope ().lookup_original ( - var, - &type (), - &name, - g != nullptr ? &g->type () : nullptr, - g != nullptr ? &g->name : nullptr)); + auto key = [locked] (const target* t) + { + return locked ? t->key_locked () : t->key (); + }; + + target_key tk (key (this)); + target_key g1k (g1 != nullptr ? key (g1) : target_key {}); + target_key g2k (g2 != nullptr ? key (g2) : target_key {}); + + if (bs == nullptr) + bs = &base_scope (); + + auto p (bs->lookup_original (var, + &tk, + g1 != nullptr ? &g1k : nullptr, + g2 != nullptr ? &g2k : nullptr)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; @@ -200,14 +233,30 @@ namespace build2 } value& target:: - append (const variable& var) + append (const variable& var, const scope* bs) { // Note: see also prerequisite::append() if changing anything here. // Note that here we want the original value without any overrides // applied. // - auto l (lookup_original (var).first); + auto l (lookup_original (var, false, bs).first); + + if (l.defined () && l.belongs (*this)) // Existing var in this target. + return vars.modify (l); // Ok since this is original. + + value& r (assign (var)); // NULL. + + if (l.defined ()) + r = *l; // Copy value (and type) from the outer scope. + + return r; + } + + value& target:: + append_locked (const variable& var, const scope* bs) + { + auto l (lookup_original (var, false, bs, true /* locked */).first); if (l.defined () && l.belongs (*this)) // Existing var in this target. return vars.modify (l); // Ok since this is original. @@ -232,7 +281,7 @@ namespace build2 r.first = lookup_type (*p.first, p.second, vars); } - // Delegate to target's find_original(). + // Delegate to target's lookup_original(). // if (!r.first) { @@ -250,100 +299,405 @@ namespace build2 { assert (!v.empty ()); - // We treat a single trailing dot as "specified no extension", double dots - // as a single trailing dot (that is, an escape sequence which can be - // repeated any number of times; in such cases we naturally assume there - // is no default extension) and triple dots as "unspecified (default) - // extension" (used when the extension in the name is not "ours", for - // example, cxx{foo.test...} for foo.test.cxx). An odd number of dots - // other than one or three is invalid. + // Normally, we treat the rightmost dot as an extension separator (but see + // find_extension() for the exact semantics) and if none exists, then we + // assume the extension is not specified. There are, however, special + // cases that override this rule: // - optional<string> r; + // - We treat triple dots as the "chosen extension separator" (used to + // resolve ambiguity as to which dot is the separator, for example, + // libfoo...u.a). If they are trailing triple dots, then this signifies + // the "unspecified (default) extension" (used when the extension in the + // name is not "ours", for example, cxx{foo.test...} for foo.test.cxx) + // Having multiple triple dots is illegal. + // + // - Otherwise, we treat a single trailing dot as the "specified no + // - extension". + // + // - Finally, double dots are used as an escape sequence to make sure the + // dot is not treated as an extension separator (or as special by any of + // the above rules, for example, libfoo.u..a). In case of trailing + // double dots, we naturally assume there is no default extension. + // + // An odd number of dots other than one or three is illegal. This means, + // in particular, that it's impossible to specify a base/extension pair + // where either the base ends with a dot or the extension begins with one + // (or both). We are ok with that. + // + // Dot-only sequences are illegal. Note though, that dir{.} and dir{..} + // are handled ad hoc outside this function and are valid. + + // Note that we cannot unescape dots in-place before we validate the name + // since it can be required for diagnostics. Thus, the plan is as follows: + // + // - Iterate right to left, searching for the extension dot, validating + // the name, and checking if any dots are escaped. + // + // - Split the name. + // + // - Unescape the dots in the name and/or extension, if required. + + // Search for an extension dot, validate the name, and check for escape + // sequences. + // + optional<size_t> edp; // Extension dot position. + size_t edn (0); // Extension dot representation lenght (1 or 3). + + bool escaped (false); + bool dot_only (true); + size_t n (v.size ()); + + // Iterate right to left until the beginning of the string or a directory + // separator is encountered. + // + // At the end of the loop p will point to the beginning of the leaf. + // + size_t p (n - 1); - size_t p; - if (v.back () != '.') + for (;; --p) { - if ((p = path::traits_type::find_extension (v)) != string::npos) - r = string (v.c_str () + p + 1); + char c (v[p]); + + if (c == '.') + { + // Find the first dot in the sequence. + // + size_t i (p); + for (; i != 0 && v[i - 1] == '.'; --i) ; + + size_t sn (p - i + 1); // Sequence length. + + if (sn == 3) // Triple dots? + { + if (edp && edn == 3) + fail (loc) << "multiple triple dots in target name '" << v << "'"; + + edp = i; + edn = 3; + } + else if (sn == 1) // Single dot? + { + if (!edp) + { + edp = i; + edn = 1; + } + } + else if (sn % 2 == 0) // Escape sequence? + escaped = true; + else + fail (loc) << "invalid dot sequence in target name '" << v << "'"; + + p = i; // Position to the first dot in the sequence. + } + else if (path::traits_type::is_separator (c)) + { + // Position to the beginning of the leaf and bail out. + // + ++p; + break; + } + else + dot_only = false; + + if (p == 0) + break; } - else + + if (dot_only) + fail (loc) << "invalid target name '" << v << "'"; + + // The leading dot cannot be an extension dot. Thus, the leading triple + // dots are invalid and the leading single dot is not considered as such. + // + if (edp && *edp == p) { - if ((p = v.find_last_not_of ('.')) == string::npos) - fail (loc) << "invalid target name '" << v << "'"; + if (edn == 3) + fail (loc) << "leading triple dots in target name '" << v << "'"; + + edp = nullopt; + } - p++; // Position of the first trailing dot. - size_t n (v.size () - p); // Number of the trailing dots. + // Split the name. + // + optional<string> r; - if (n == 1) + if (edp) + { + if (*edp != n - edn) // Non-trailing dot? + r = string (v, *edp + edn); + else if (edn == 1) // Trailing single dot? r = string (); - else if (n == 3) - ; - else if (n % 2 == 0) + //else if (edn == 3) // Trailing triple dots? + // r = nullopt; + + v.resize (*edp); + } + else if (v.back () == '.') // Trailing escaped dot? + r = string (); + + if (!escaped) + return r; + + // Unescape the dots. + // + auto unescape = [] (string& s, size_t b = 0) + { + size_t n (s.size ()); + for (size_t i (b); i != n; ++i) { - p += n / 2; // Keep half of the dots. - r = string (); + if (s[i] == '.') + { + // Find the end of the dot sequence. + // + size_t j (i + 1); + for (; j != n && s[j] == '.'; ++j) ; + + size_t sn (j - i); // Sequence length. + + // Multiple dots can only represent an escape sequence now. + // + if (sn != 1) + { + assert (sn % 2 == 0); + + size_t dn (sn / 2); // Number of dots to remove. + s.erase (i + dn, dn); + + i += dn - 1; // Position to the last dot in the sequence. + n -= dn; // Adjust string size counter. + } + } } - else - fail (loc) << "invalid trailing dot sequence in target name '" - << v << "'"; - } + }; - if (p != string::npos) - v.resize (p); + unescape (v, p); + + if (r) + unescape (*r); return r; } + // Escape the name according to the rules described in split_name(). The + // idea is that we should be able to roundtrip things. + // + // Note though, that multiple representations can end up with the same + // name, for example libfoo.u..a and libfoo...u.a. We will always resolve + // ambiguity with the triple dot and only escape those dots that otherwise + // can be misinterpreted (dot sequences, etc). + // void target:: combine_name (string& v, const optional<string>& e, bool de) { - if (v.back () == '.') + // Escape all dot sequences since they can be misinterpreted as escape + // sequences and return true if the result contains an unescaped dot that + // can potentially be considered an extension dot. + // + // In the name mode only consider the basename, escape the trailing dot + // (since it can be misinterpreted as the 'no extension' case), and don't + // treat the basename leading dot as the potential extension dot. + // + auto escape = [] (string& s, bool name) -> bool { - assert (e && e->empty ()); + if (s.empty ()) + return false; - size_t p (v.find_last_not_of ('.')); - assert (p != string::npos); + bool r (false); + size_t n (s.size ()); - p++; // Position of the first trailing dot. - size_t n (v.size () - p); // Number of the trailing dots. - v.append (n, '.'); // Double them. - } - else if (e) + // Iterate right to left until the beginning of the string or a + // directory separator is encountered. + // + for (size_t p (n - 1);; --p) + { + char c (s[p]); + + if (c == '.') + { + // Find the first dot in the sequence. + // + size_t i (p); + for (; i != 0 && s[i - 1] == '.'; --i) ; + + size_t sn (p - i + 1); // Sequence length. + + bool esc (sn != 1); // Escape the sequence. + bool ext (sn == 1); // An extension dot, potentially. + + if (name) + { + if (i == n - 1) + esc = true; + + if (ext && (i == 0 || path::traits_type::is_separator (s[i - 1]))) + ext = false; + } + + if (esc) + s.insert (p + 1, sn, '.'); // Double them. + + if (ext) + r = true; + + p = i; // Position to the first dot in the sequence. + } + else if (path::traits_type::is_separator (c)) + { + assert (name); + break; + } + + if (p == 0) + break; + } + + return r; + }; + + bool ed (escape (v, true /* name */)); + + if (v.back () == '.') // Name had (before escaping) trailing dot. { - v += '.'; - v += *e; // Empty or not. + assert (e && e->empty ()); } - else if (de) + else if (e) { - if (path::traits_type::find_extension (v) != string::npos) - v += "..."; + // Separate the name and extension with the triple dots if the extension + // contains potential extension dots. + // + string ext (*e); + v += escape (ext, false /* name */) ? "..." : "."; + v += ext; // Empty or not. } + else if (de && ed) + v += "..."; } // include() // + // See var_include documentation for details on what's going on here. + // include_type include_impl (action a, const target& t, - const string& v, const prerequisite& p, - const target* m) + const target* m, + lookup* rl) { context& ctx (t.ctx); - include_type r (false); + include_type r (include_type::normal); + { + lookup l (p.vars[ctx.var_include]); - if (v == "false") r = include_type::excluded; - else if (v == "adhoc") r = include_type::adhoc; - else if (v == "true") r = include_type::normal; - else - fail << "invalid " << ctx.var_include->name << " variable value " - << "'" << v << "' specified for prerequisite " << p; + if (l.defined ()) + { + if (l->null) + { + // @@ TMP (added in 0.16.0). + // + warn << "null " << *ctx.var_include << " variable value specified " + << "for prerequisite " << p << + info << "treated as undefined for backwards compatibility" << + info << "this warning will become error in the future"; + } + else + { + const string& v (cast<string> (*l)); + + if (v == "false") r = include_type::excluded; + else if (v == "true") r = include_type::normal; + else if (v == "adhoc") r = include_type::adhoc; + else if (v == "posthoc") r = include_type::posthoc; + else + fail << "invalid " << *ctx.var_include << " variable value '" + << v << "' specified for prerequisite " << p; + } + } + } + + // Handle operation-specific override (see var_include documentation + // for details). + // + lookup l; + optional<bool> r1; // Absent means something other than true|false. + + names storage; + names_view ns; + const variable* ovar (nullptr); + + if (r != include_type::excluded) + { + // Instead of going via potentially expensive target::base_scope(), use + // the prerequisite's scope; while it may not be the same as the + // targets's base scope, they must have the same root scope. + // + const scope& rs (*p.scope.root_scope ()); + + ovar = rs.root_extra->operations[ + (a.outer () + ? ctx.current_outer_oif + : ctx.current_inner_oif)->id].ovar; + + if (ovar != nullptr) + { + l = p.vars[*ovar]; + + if (l.defined ()) + { + if (l->null) + fail << "null " << *ovar << " variable value specified for " + << "prerequisite " << p; + + // Maybe we should optimize this for the common cases (bool, path, + // name)? But then again we don't expect many such overrides. Plus + // will complicate the diagnostics below. + // + ns = reverse (*l, storage, true /* reduce */); + + if (ns.size () == 1) + { + const name& n (ns[0]); + + if (n.simple ()) + { + const string& v (n.value); + + if (v == "false") + r1 = false; + else if (v == "true") + r1 = true; + } + } + + if (r1 && !*r1) + r = include_type::excluded; + } + } + } // Call the meta-operation override, if any (currently used by dist). // - if (auto f = ctx.current_mif->include) - r = f (a, t, prerequisite_member {p, m}, r); + if (r != include_type::normal || l) + { + if (auto f = ctx.current_mif->include) + r = f (a, t, prerequisite_member {p, m}, r, l); + } + + if (l) + { + if (rl != nullptr) + *rl = l; + else if (!r1) + { + // Note: we have to delay this until the meta-operation callback above + // had a chance to override it. + // + fail << "unrecognized " << *ovar << " variable value '" << ns + << "' specified for prerequisite " << p; + } + } return r; } @@ -353,7 +707,9 @@ namespace build2 const target* target_set:: find (const target_key& k, tracer& trace) const { - slock sl (mutex_); + bool load (ctx.phase == run_phase::load); + + slock sl (mutex_, defer_lock); if (!load) sl.lock (); map_type::const_iterator i (map_.find (k)); if (i == map_.end ()) @@ -372,14 +728,18 @@ namespace build2 // Between us releasing the shared lock and acquiring unique the // extension could change and possibly a new target that matches the // key could be inserted. In this case we simply re-run find (). + // Naturally, can't happen during load. // - sl.unlock (); - ul = ulock (mutex_); - - if (ext) // Someone set the extension. + if (!load) { - ul.unlock (); - return find (k, trace); + sl.unlock (); + ul = ulock (mutex_); + + if (ext) // Someone set the extension. + { + ul.unlock (); + return find (k, trace); + } } } @@ -413,10 +773,12 @@ namespace build2 string name, optional<string> ext, target_decl decl, - tracer& trace) + tracer& trace, + bool skip_find, + bool need_lock) { target_key tk {&tt, &dir, &out, &name, move (ext)}; - target* t (const_cast<target*> (find (tk, trace))); + target* t (skip_find ? nullptr : const_cast<target*> (find (tk, trace))); if (t == nullptr) { @@ -437,7 +799,9 @@ namespace build2 // case we proceed pretty much like find() except already under the // exclusive lock. // - ulock ul (mutex_); + ulock ul (mutex_, defer_lock); + if (ctx.phase != run_phase::load || need_lock) + ul.lock (); auto p (map_.emplace (target_key {&tt, &t->dir, &t->out, &t->name, e}, unique_ptr<target> (t))); @@ -446,10 +810,28 @@ namespace build2 if (p.second) { +#if 0 + { + size_t n (map_.bucket_count ()); + if (n > buckets_) + { + text << "target_set buckets: " << buckets_ << " -> " << n + << " (" << map_.size () << ")"; + buckets_ = n; + } + } +#endif + t->ext_ = &i->first.ext; t->decl = decl; t->state.inner.target_ = t; t->state.outer.target_ = t; + t->state.inner.vars.target_ = t; + t->state.outer.vars.target_ = t; + + if (ctx.phase != run_phase::load && !need_lock) + ul.unlock (); + return pair<target&, ulock> (*t, move (ul)); } @@ -499,9 +881,16 @@ namespace build2 return pair<target&, ulock> (*t, ulock ()); } - ostream& - to_stream (ostream& os, const target_key& k, optional<stream_verbosity> osv) + static const optional<string> unknown_ext ("?"); + + bool + to_stream (ostream& os, + const target_key& k, + optional<stream_verbosity> osv, + bool name_only) { + // Note: similar code in print_diag_impl(vector<target_key>). + stream_verbosity sv (osv ? *osv : stream_verb (os)); uint16_t dv (sv.path); uint16_t ev (sv.extension); @@ -511,26 +900,33 @@ namespace build2 // bool n (!k.name->empty ()); - // Note: relative() returns empty for './'. - // - const dir_path& rd (dv < 1 ? relative (*k.dir) : *k.dir); // Relative. - const dir_path& pd (n ? rd : rd.directory ()); // Parent. + const target_type& tt (*k.type); - if (!pd.empty ()) + dir_path rds; // Storage. + if (!name_only) { + // Note: relative() returns empty for './'. + // if (dv < 1) - os << diag_relative (pd); - else - to_stream (os, pd, true /* representation */); - } + rds = relative (*k.dir); - const target_type& tt (*k.type); + const dir_path& rd (dv < 1 ? rds : *k.dir); // Relative. + const dir_path& pd (n ? rd : rd.directory ()); // Parent. - os << tt.name << '{'; + if (!pd.empty ()) + { + if (dv < 1) + os << diag_relative (pd); + else + to_stream (os, pd, true /* representation */); + } + + os << tt.name << '{'; + } if (n) { - os << *k.name; + const optional<string>* ext (nullptr); // NULL or present. // If the extension derivation functions are NULL, then it means this // target type doesn't use extensions. @@ -543,44 +939,73 @@ namespace build2 // if (ev > 0 && (ev > 1 || (k.ext && !k.ext->empty ()))) { - os << '.' << (k.ext ? *k.ext : "?"); + ext = k.ext ? &k.ext : &unknown_ext; } } else assert (!k.ext || k.ext->empty ()); // Unspecified or none. + + // Escape dots in the name/extension to resolve potential ambiguity. + // + if (k.name->find ('.') == string::npos && + (ext == nullptr || (*ext)->find ('.') == string::npos)) + { + os << *k.name; + + if (ext != nullptr) + os << '.' << **ext; + } + else + { + string n (*k.name); + target::combine_name (n, + ext != nullptr ? *ext : nullopt_string, + false /* default_extension */); + os << n; + } } else + { + if (name_only && dv < 1) // Already done if !name_only. + rds = relative (*k.dir); + + const dir_path& rd (dv < 1 ? rds : *k.dir); + to_stream (os, rd.empty () ? dir_path (".") : rd.leaf (), true /* representation */); + } - os << '}'; - - // If this target is from src, print its out. - // - if (!k.out->empty ()) + if (!name_only) { - if (dv < 1) + os << '}'; + + // If this target is from src, print its out. + // + if (!k.out->empty ()) { - // Don't print '@./'. - // - const string& o (diag_relative (*k.out, false)); + if (dv < 1) + { + // Don't print '@./'. + // + const string& o (diag_relative (*k.out, false)); - if (!o.empty ()) - os << '@' << o; + if (!o.empty ()) + os << '@' << o; + } + else + os << '@' << *k.out; } - else - os << '@' << *k.out; } - return os; + return n; // Regular if we had the name. } ostream& operator<< (ostream& os, const target_key& k) { if (auto p = k.type->print) - p (os, k); + p (os, k, false /* name_only */); else to_stream (os, k, stream_verb (os)); @@ -601,14 +1026,19 @@ namespace build2 case run_phase::load: break; case run_phase::match: { - // Similar logic to matched_state_impl(). + // Similar logic to target::matched(). // const opstate& s (state[action () /* inner */]); - // Note: already synchronized. - size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); + // Note: use acquire for group_state(). + // + size_t c (s.task_count.load (memory_order_acquire)); + size_t b (ctx.count_base ()); // Note: cannot do (c - b)! - if (o != offset_applied && o != offset_executed) + if (!(c == (b + offset_applied) || + c == (b + offset_executed) || + (c >= (b + offset_busy) && + s.match_extra.cur_options_.load (memory_order_relaxed) != 0))) break; } // Fall through. @@ -731,41 +1161,60 @@ namespace build2 // const target* - target_search (const target& t, const prerequisite_key& pk) + target_search (context& ctx, const target*, const prerequisite_key& pk) { // The default behavior is to look for an existing target in the // prerequisite's directory scope. // - return search_existing_target (t.ctx, pk); + return search_existing_target (ctx, pk, true /* out_only */); } const target* - file_search (const target& t, const prerequisite_key& pk) + file_search (context& ctx, const target* t, const prerequisite_key& pk) { - // First see if there is an existing target. + // First see if there is an existing target in the out or src tree. // - if (const target* e = search_existing_target (t.ctx, pk)) + if (const target* e = search_existing_target (ctx, + pk, + false /* out_only */)) return e; // Then look for an existing file in the src tree. // - return search_existing_file (t.ctx, pk); + return t != nullptr ? search_existing_file (ctx, pk) : nullptr; + } + + extern const char target_extension_none_[] = ""; + + const char* + target_extension_none (const target_key& k, const scope* s) + { + return target_extension_fix<target_extension_none_> (k, s); } - void - target_print_0_ext_verb (ostream& os, const target_key& k) + const char* + target_extension_must (const target_key& tk, const scope*) + { + if (!tk.ext) + fail << tk.type->name << " target " << tk << " must include extension"; + + return tk.ext->c_str (); + } + + bool + target_print_0_ext_verb (ostream& os, const target_key& k, bool no) { stream_verbosity sv (stream_verb (os)); if (sv.extension == 1) sv.extension = 0; // Remap 1 to 0. - to_stream (os, k, sv); + return to_stream (os, k, sv, no); } - void - target_print_1_ext_verb (ostream& os, const target_key& k) + bool + target_print_1_ext_verb (ostream& os, const target_key& k, bool no) { stream_verbosity sv (stream_verb (os)); if (sv.extension == 0) sv.extension = 1; // Remap 0 to 1. - to_stream (os, k, sv); + return to_stream (os, k, sv, no); } // type info @@ -781,7 +1230,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none, }; const target_type mtime_target::static_type @@ -794,7 +1243,7 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; const target_type path_target::static_type @@ -807,33 +1256,95 @@ namespace build2 nullptr, nullptr, &target_search, - false + target_type::flag::none }; - extern const char file_ext_def[] = ""; - const target_type file::static_type { "file", &path_target::static_type, &target_factory<file>, - &target_extension_fix<file_ext_def>, + &target_extension_none, nullptr, /* default_extension */ nullptr, /* pattern */ &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, - false + target_type::flag::none + }; + + // group + // + group_view group:: + group_members (action a) const + { + if (members_on == 0) // Not yet discovered. + return group_view {nullptr, 0}; + + // Members discovered during anything other than perform_update are only + // good for that operation. For example, we only return the static members + // ("representative sample") for perform_configure. + // + // We also re-discover the members on each update and clean not to + // overcomplicate the already twisted adhoc_buildscript_rule::apply() + // logic. + // + if (members_on != ctx.current_on) + { + if (members_action != perform_update_id || + a == perform_update_id || + a == perform_clean_id) + return group_view {nullptr, 0}; + } + + // Note that we may have no members (e.g., perform_configure and there are + // no static members). However, whether std::vector returns a non-NULL + // pointer in this case is undefined. + // + size_t n (members.size ()); + return group_view { + n != 0 + ? members.data () + : reinterpret_cast<const target* const*> (this), + n}; + } + + const target_type group::static_type + { + "group", + &mtime_target::static_type, + &target_factory<group>, + nullptr, + nullptr, + nullptr, + nullptr, + &target_search, + // + // Note that the dyn_members semantics is used not only to handle + // depdb-dyndep --dyn-target, but also pattern rule-static members. + // + target_type::flag::group | target_type::flag::dyn_members }; + // alias + // static const target* - alias_search (const target& t, const prerequisite_key& pk) + alias_search (context& ctx, const target* t, const prerequisite_key& pk) { // For an alias we don't want to silently create a target since it will do - // nothing and it most likely not what the user intended. + // nothing and it most likely not what the user intended (but omit this + // check when searching for an existing target since presumably a new one + // won't be created in this case). + // + // But, allowing implied aliases seems harmless since all the alias does + // is pull its prerequisites. And they are handy to use as metadata + // carriers. // - const target* e (search_existing_target (t.ctx, pk)); + // Doesn't feel like an alias in the src tree makes much sense. + // + const target* e (search_existing_target (ctx, pk, true /* out_only */)); - if (e == nullptr || e->decl != target_decl::real) + if ((e == nullptr || + !(operator>= (e->decl, target_decl::implied))) && t != nullptr) fail << "no explicit target for " << pk; return e; @@ -849,7 +1360,7 @@ namespace build2 nullptr, nullptr, &alias_search, - false + target_type::flag::none }; // dir @@ -859,7 +1370,7 @@ namespace build2 { try { - for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */)) + for (const dir_entry& e: dir_iterator (d, dir_iterator::detect_dangling)) { switch (e.type ()) { @@ -877,6 +1388,16 @@ namespace build2 break; } + case entry_type::unknown: + { + bool sl (e.ltype () == entry_type::symlink); + + warn << "skipping " + << (sl ? "dangling symlink" : "inaccessible entry") << ' ' + << d / e.path (); + + break; + } default: break; } @@ -898,17 +1419,26 @@ namespace build2 try { - for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */)) + for (const dir_entry& e: dir_iterator (d, dir_iterator::detect_dangling)) { if (e.type () == entry_type::directory) + { r.push_back ( - prerequisite (nullopt, - dir::static_type, + prerequisite (dir::static_type, dir_path (e.path ().representation ()), // Relative. dir_path (), // In the out tree. string (), nullopt, bs)); + } + else if (e.type () == entry_type::unknown) + { + bool sl (e.ltype () == entry_type::symlink); + + warn << "skipping " + << (sl ? "dangling symlink" : "inaccessible entry") << ' ' + << d / e.path (); + } } } catch (const system_error& e) @@ -920,17 +1450,27 @@ namespace build2 } static const target* - dir_search (const target& t, const prerequisite_key& pk) + dir_search (context& ctx, const target* t, const prerequisite_key& pk) { tracer trace ("dir_search"); - // The first step is like in search_alias(): looks for an existing target. + // The first step is like in alias_search(): looks for an existing target + // (but unlike alias, no implied, think `test/: install=false`). + // + // Likewise, dir{} in the src tree doesn't make much sense. // - const target* e (search_existing_target (t.ctx, pk)); + const target* e (search_existing_target (ctx, pk, true /* out_only */)); if (e != nullptr && e->decl == target_decl::real) return e; + // The search for an existing target can also be done during execute so + // none of the below code applied. Note: return implied instead of NULL + // (to be consistent with search_new(), for example). + // + if (t == nullptr) + return e; + // If not found (or is implied), then try to load the corresponding // buildfile (which would normally define this target). Failed that, see // if we can assume an implied buildfile which would be equivalent to: @@ -964,18 +1504,18 @@ namespace build2 // bool retest (false); - assert (t.ctx.phase == run_phase::match); + assert (ctx.phase == run_phase::match); { // Switch the phase to load. // - phase_switch ps (t.ctx, run_phase::load); + phase_switch ps (ctx, run_phase::load); // This is subtle: while we were fussing around another thread may have // loaded the buildfile. So re-test now that we are in an exclusive // phase. // if (e == nullptr) - e = search_existing_target (t.ctx, pk); + e = search_existing_target (ctx, pk, true); if (e != nullptr && e->decl == target_decl::real) retest = true; @@ -983,6 +1523,10 @@ namespace build2 { // Ok, no luck, switch the scope. // + // Note that we don't need to do anything for the project's + // environment: source_once() will take care of it itself and + // search_implied() is not affected. + // pair<scope&, scope*> sp ( switch_scope (*s.rw ().root_scope (), out_base)); @@ -1009,14 +1553,14 @@ namespace build2 } } - assert (t.ctx.phase == run_phase::match); + assert (ctx.phase == run_phase::match); // If we loaded/implied the buildfile, examine the target again. // if (retest) { if (e == nullptr) - e = search_existing_target (t.ctx, pk); + e = search_existing_target (ctx, pk, true); if (e != nullptr && e->decl == target_decl::real) return e; @@ -1061,7 +1605,7 @@ namespace build2 &dir_pattern, nullptr, &dir_search, - false + target_type::flag::none }; const target_type fsdir::static_type @@ -1074,7 +1618,7 @@ namespace build2 &dir_pattern, nullptr, &target_search, - false + target_type::flag::none }; static optional<string> @@ -1141,8 +1685,8 @@ namespace build2 nullptr, #endif nullptr, - &file_search, - false + &file_search, // Note: can also be a script in src. + target_type::flag::none }; static const char* @@ -1228,7 +1772,56 @@ namespace build2 &buildfile_target_pattern, nullptr, &file_search, - false + target_type::flag::none + }; + + static const char* + buildscript_target_extension (const target_key& tk, const scope*) + { + // If the name is special 'buildscript', then there is no extension, + // otherwise it is .buildscript. + // + return *tk.name == "buildscript" ? "" : "buildscript"; + } + + static bool + buildscript_target_pattern (const target_type&, + const scope&, + string& v, + optional<string>& e, + const location& l, + bool r) + { + if (r) + { + assert (e); + e = nullopt; + } + else + { + e = target::split_name (v, l); + + if (!e && v != "buildscript") + { + e = "buildscript"; + return true; + } + } + + return false; + } + + const target_type buildscript::static_type + { + "buildscript", + &file::static_type, + &target_factory<buildscript>, + &buildscript_target_extension, + nullptr, /* default_extension */ + &buildscript_target_pattern, + nullptr, + &file_search, + target_type::flag::none }; const target_type doc::static_type @@ -1236,12 +1829,12 @@ namespace build2 "doc", &file::static_type, &target_factory<doc>, - &target_extension_fix<file_ext_def>, // Same as file (no extension). + &target_extension_none, // Same as file (no extension). nullptr, /* default_extension */ nullptr, /* pattern */ // Same as file. &target_print_1_ext_verb, // Same as file. &file_search, - false + target_type::flag::none }; const target_type legal::static_type @@ -1249,34 +1842,25 @@ namespace build2 "legal", &doc::static_type, &target_factory<legal>, - &target_extension_fix<file_ext_def>, // Same as file (no extension). + &target_extension_none, // Same as file (no extension). nullptr, /* default_extension */ nullptr, /* pattern */ // Same as file. &target_print_1_ext_verb, // Same as file. &file_search, - false + target_type::flag::none }; - static const char* - man_extension (const target_key& tk, const scope*) - { - if (!tk.ext) - fail << "man target " << tk << " must include extension (man section)"; - - return tk.ext->c_str (); - } - const target_type man::static_type { "man", &doc::static_type, &target_factory<man>, - &man_extension, // Should be specified explicitly. + &target_extension_must, // Should be specified explicitly. nullptr, /* default_extension */ nullptr, &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, - false + target_type::flag::none }; extern const char man1_ext[] = "1"; // VC14 rejects constexpr. @@ -1291,7 +1875,7 @@ namespace build2 &target_pattern_fix<man1_ext>, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, - false + target_type::flag::none }; static const char* @@ -1340,6 +1924,6 @@ namespace build2 &manifest_target_pattern, nullptr, &file_search, - false + target_type::flag::none }; } |