// file : libbuild2/target.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2019 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #include #include #include #include #include #include #include using namespace std; using namespace butl; namespace build2 { // target_type // 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_state // static const char* const target_state_[] = { "unknown", "unchanged", "postponed", "busy", "changed", "failed", "group" }; ostream& operator<< (ostream& os, target_state ts) { return os << target_state_[static_cast (ts)]; } // recipe // const recipe empty_recipe; const recipe noop_recipe (&noop_action); const recipe default_recipe (&default_action); const recipe group_recipe (&group_action); // target // const target::prerequisites_type target::empty_prerequisites_; target:: ~target () { clear_data (); } const string& target:: ext (string v) { ulock l (ctx.targets.mutex_); // Once the extension is set, it is immutable. However, it is possible // that someone has already "branded" this target with a different // extension. // optional& e (*ext_); if (!e) e = move (v); else if (*e != v) { string o (*e); l.unlock (); fail << "conflicting extensions '" << o << "' and '" << v << "' " << "for target " << *this; } return *e; } group_view target:: group_members (action) const { assert (false); // Not a group or doesn't expose its members. return group_view {nullptr, 0}; } const scope& target:: base_scope () 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& target:: root_scope () const { // This is tricky to cache so we do the lookup for now. // const scope* r (base_scope ().root_scope ()); assert (r != nullptr); return *r; } pair target:: find_original (const variable& var, bool target_only) const { pair r (lookup (), 0); ++r.second; { auto p (vars.find (var)); if (p.first != nullptr) r.first = lookup (*p.first, p.second, vars); } const target* g (nullptr); if (!r.first) { ++r.second; // Skip looking up in the ad hoc group, which is semantically the // first/primary member. // if ((g = group == nullptr ? nullptr : group->adhoc_group () ? group->group : group)) { auto p (g->vars.find (var)); if (p.first != nullptr) r.first = lookup (*p.first, p.second, g->vars); } } // Delegate to scope's find_original(). // if (!r.first) { if (!target_only) { auto p (base_scope ().find_original ( var, &type (), &name, g != nullptr ? &g->type () : nullptr, g != nullptr ? &g->name : nullptr)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; } else r.second = size_t (~0); } return r; } value& target:: append (const variable& var) { // Note: see also prerequisite::append() if changing anything here. // Note that here we want the original value without any overrides // applied. // lookup l (find_original (var).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; } pair target::opstate:: find_original (const variable& var, bool target_only) const { pair r (lookup (), 0); ++r.second; { auto p (vars.find (var)); if (p.first != nullptr) r.first = lookup (*p.first, p.second, vars); } // Delegate to target's find_original(). // if (!r.first) { auto p (target_->find_original (var, target_only)); r.first = move (p.first); r.second = r.first ? r.second + p.second : p.second; } return r; } optional target:: split_name (string& v, const location& loc) { 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. // optional r; size_t p; if (v.back () != '.') { if ((p = path::traits_type::find_extension (v)) != string::npos) r = string (v.c_str () + p + 1); } else { if ((p = v.find_last_not_of ('.')) == string::npos) fail (loc) << "invalid target name '" << v << "'"; p++; // Position of the first trailing dot. size_t n (v.size () - p); // Number of the trailing dots. if (n == 1) r = string (); else if (n == 3) ; else if (n % 2 == 0) { p += n / 2; // Keep half of the dots. r = string (); } else fail (loc) << "invalid trailing dot sequence in target name '" << v << "'"; } if (p != string::npos) v.resize (p); return r; } void target:: combine_name (string& v, const optional& e, bool de) { if (v.back () == '.') { assert (e && e->empty ()); size_t p (v.find_last_not_of ('.')); assert (p != string::npos); 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) { v += '.'; v += *e; // Empty or not. } else if (de) { if (path::traits_type::find_extension (v) != string::npos) v += "..."; } } // include() // include_type include_impl (action a, const target& t, const string& v, const prerequisite& p, const target* m) { context& ctx (t.ctx); include_type r (false); 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; // 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); return r; } // target_set // const target* target_set:: find (const target_key& k, tracer& trace) const { slock sl (mutex_); map_type::const_iterator i (map_.find (k)); if (i == map_.end ()) return nullptr; const target& t (*i->second); optional& ext (i->first.ext); if (ext != k.ext) { ulock ul; // Keep locked for trace. if (k.ext) { // To update the extension we have to re-lock for exclusive access. // 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 (). // sl.unlock (); ul = ulock (mutex_); if (ext) // Someone set the extension. { ul.unlock (); return find (k, trace); } } l5 ([&]{ diag_record r (trace); r << "assuming target "; to_stream (r.os, target_key {&t.type (), &t.dir, &t.out, &t.name, ext}, stream_verb_max); // Always print the extension. r << " is the same as the one with "; if (!k.ext) r << "unspecified extension"; else if (k.ext->empty ()) r << "no extension"; else r << "extension " << *k.ext; }); if (k.ext) ext = k.ext; } return &t; } pair target_set:: insert_locked (const target_type& tt, dir_path dir, dir_path out, string name, optional ext, bool implied, tracer& trace) { target_key tk {&tt, &dir, &out, &name, move (ext)}; target* t (const_cast (find (tk, trace))); if (t == nullptr) { // We sometimes call insert() even if we expect to find an existing // target in order to keep the same code (see cc/search_library()). // assert (ctx.phase != run_phase::execute); optional e ( tt.fixed_extension != nullptr ? string (tt.fixed_extension (tk, nullptr /* root scope */)) : move (tk.ext)); t = tt.factory (ctx, tt, move (dir), move (out), move (name)); // Re-lock for exclusive access. In the meantime, someone could have // inserted this target so emplace() below could return false, in which // case we proceed pretty much like find() except already under the // exclusive lock. // ulock ul (mutex_); auto p (map_.emplace (target_key {&tt, &t->dir, &t->out, &t->name, e}, unique_ptr (t))); map_type::iterator i (p.first); if (p.second) { t->ext_ = &i->first.ext; t->implied = implied; t->state.data[0].target_ = t; t->state.data[1].target_ = t; return pair (*t, move (ul)); } // The "tail" of find(). // t = i->second.get (); optional& ext (i->first.ext); if (ext != e) { l5 ([&]{ diag_record r (trace); r << "assuming target "; to_stream ( r.os, target_key {&t->type (), &t->dir, &t->out, &t->name, ext}, stream_verb_max); // Always print the extension. r << " is the same as the one with "; if (!e) r << "unspecified extension"; else if (e->empty ()) r << "no extension"; else r << "extension " << *e; }); if (e) ext = e; } // Fall through (continue as if the first find() returned this target). } if (!implied) { // The implied flag can only be cleared during the load phase. // assert (ctx.phase == run_phase::load); // Clear the implied flag. // if (t->implied) t->implied = false; } return pair (*t, ulock ()); } ostream& to_stream (ostream& os, const target_key& k, optional osv) { stream_verbosity sv (osv ? *osv : stream_verb (os)); uint16_t dv (sv.path); uint16_t ev (sv.extension); // If the name is empty, then we want to print the last component of the // directory inside {}, e.g., dir{bar/}, not bar/dir{}. // 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. if (!pd.empty ()) { if (dv < 1) os << diag_relative (pd); else os << pd.representation (); } const target_type& tt (*k.type); os << tt.name << '{'; if (n) { os << *k.name; // If the extension derivation functions are NULL, then it means this // target type doesn't use extensions. // if (tt.fixed_extension != nullptr || tt.default_extension != nullptr) { // For verbosity level 0 we don't print the extension. For 1 we print // it if there is one. For 2 we print 'foo.?' if it hasn't yet been // assigned and 'foo.' if it is assigned as "no extension" (empty). // if (ev > 0 && (ev > 1 || (k.ext && !k.ext->empty ()))) { os << '.' << (k.ext ? *k.ext : "?"); } } else assert (!k.ext); } else os << (rd.empty () ? dir_path (".") : rd.leaf ()).representation (); os << '}'; // If this target is from src, print its out. // if (!k.out->empty ()) { if (dv < 1) { // Don't print '@./'. // const string& o (diag_relative (*k.out, false)); if (!o.empty ()) os << '@' << o; } else os << '@' << *k.out; } return os; } ostream& operator<< (ostream& os, const target_key& k) { if (auto p = k.type->print) p (os, k); else to_stream (os, k, stream_verb (os)); return os; } // mtime_target // timestamp mtime_target:: mtime () const { // Figure out from which target we should get the value. // const mtime_target* t (this); switch (ctx.phase) { case run_phase::load: break; case run_phase::match: { // Similar logic to matched_state_impl(). // const opstate& s (state[action () /* inner */]); // Note: already synchronized. size_t o (s.task_count.load (memory_order_relaxed) - ctx.count_base ()); if (o != offset_applied && o != offset_executed) break; } // Fall through. case run_phase::execute: { if (group_state (action () /* inner */)) t = &group->as (); break; } } return timestamp (duration (t->mtime_.load (memory_order_consume))); } // path_target // const string* path_target:: derive_extension (bool search, const char* de) { // See also search_existing_file() if updating anything here. // Should be no default extension if searching. // assert (!search || de == nullptr); // The target should use extensions and they should not be fixed. // assert (de == nullptr || type ().default_extension != nullptr); if (const string* p = ext ()) // Note that returning by reference is now MT-safe since once the // extension is specified, it is immutable. // return p; else { optional e; // If the target type has the default extension function then try that // first. The reason for preferring it over what's been provided by the // caller is that this function will often use the 'extension' variable // which the user can use to override extensions. But since we pass the // provided default extension, the target type can override this logic // (see the exe{} target type for a use case). // if (auto f = type ().default_extension) e = f (key (), base_scope (), de, search); if (!e) { if (de != nullptr) e = de; else { if (search) return nullptr; fail << "no default extension for target " << *this << endf; } } return &ext (move (*e)); } } const path& path_target:: derive_path (const char* de, const char* np, const char* ns) { path_type p (dir); if (np == nullptr || np[0] == '\0') p /= name; else { p /= np; p += name; } if (ns != nullptr) p += ns; return derive_path (move (p), de); } const path& path_target:: derive_path (path_type p, const char* de) { // Derive and add the extension if any. // { const string& e (derive_extension (de)); if (!e.empty ()) { p += '.'; p += e; } } path (move (p)); return path_; } // Search functions. // const target* target_search (const target& t, 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); } const target* file_search (const target& t, const prerequisite_key& pk) { // First see if there is an existing target. // if (const target* e = search_existing_target (t.ctx, pk)) return e; // Then look for an existing file in the src tree. // return search_existing_file (t.ctx, pk); } void target_print_0_ext_verb (ostream& os, const target_key& k) { stream_verbosity sv (stream_verb (os)); if (sv.extension == 1) sv.extension = 0; // Remap 1 to 0. to_stream (os, k, sv); } void target_print_1_ext_verb (ostream& os, const target_key& k) { stream_verbosity sv (stream_verb (os)); if (sv.extension == 0) sv.extension = 1; // Remap 0 to 1. to_stream (os, k, sv); } // type info // const target_type target::static_type { "target", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &target_search, false }; const target_type mtime_target::static_type { "mtime_target", &target::static_type, nullptr, nullptr, nullptr, nullptr, nullptr, &target_search, false }; const target_type path_target::static_type { "path_target", &mtime_target::static_type, nullptr, nullptr, nullptr, nullptr, nullptr, &target_search, false }; extern const char file_ext_def[] = ""; const target_type file::static_type { "file", &path_target::static_type, &target_factory, &target_extension_fix, nullptr, /* default_extension */ nullptr, /* pattern */ &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, false }; static const target* alias_search (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. // const target* e (search_existing_target (t.ctx, pk)); if (e == nullptr || e->implied) fail << "no explicit target for " << pk; return e; } const target_type alias::static_type { "alias", &target::static_type, &target_factory, nullptr, // Extension not used. nullptr, nullptr, nullptr, &alias_search, false }; // dir // bool dir:: check_implied (const scope& rs, const dir_path& d) { try { for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */)) { switch (e.type ()) { case entry_type::directory: { if (check_implied (rs, d / path_cast (e.path ()))) return true; break; } case entry_type::regular: { if (e.path () == rs.root_extra->buildfile_file) return true; break; } default: break; } } } catch (const system_error& e) { fail << "unable to iterate over " << d << ": " << e << endf; } return false; } prerequisites dir:: collect_implied (const scope& bs) { prerequisites_type r; const dir_path& d (bs.src_path ()); try { for (const dir_entry& e: dir_iterator (d, true /* ignore_dangling */)) { if (e.type () == entry_type::directory) r.push_back ( prerequisite (nullopt, dir::static_type, dir_path (e.path ().representation ()), // Relative. dir_path (), // In the out tree. string (), nullopt, bs)); } } catch (const system_error& e) { fail << "unable to iterate over " << d << ": " << e; } return r; } static const target* dir_search (const target& t, const prerequisite_key& pk) { tracer trace ("dir_search"); // The first step is like in search_alias(): looks for an existing target. // const target* e (search_existing_target (t.ctx, pk)); if (e != nullptr && !e->implied) 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: // // ./: */ // const dir_path& d (*pk.tk.dir); // We only do this for relative paths. // if (d.relative ()) { // Note: this code is a custom version of parser::parse_include(). const scope& s (*pk.scope); // Calculate the new out_base. // dir_path out_base (s.out_path () / d); out_base.normalize (); // In our world modifications to the scope structure during search & // match should be "pure append" in the sense that they should not // affect any existing targets that have already been searched & // matched. // // A straightforward way to enforce this is to not allow any existing // targets to be inside any newly created scopes (except, perhaps for // the directory target itself which we know hasn't been searched yet). // This, however, is not that straightforward to implement: we would // need to keep a directory prefix map for all the targets (e.g., in // target_set). Also, a buildfile could load from a directory that is // not a subdirectory of out_base. So for now we just assume that this // is so. And so it is. // bool retest (false); assert (t.ctx.phase == run_phase::match); { // Switch the phase to load. // phase_switch ps (t.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 exclusive // phase. // if (e == nullptr) e = search_existing_target (t.ctx, pk); if (e != nullptr && !e->implied) retest = true; else { // Ok, no luck, switch the scope. // pair sp ( switch_scope (*s.rw ().root_scope (), out_base)); if (sp.second != nullptr) // Ignore scopes out of any project. { scope& base (sp.first); scope& root (*sp.second); const dir_path& src_base (base.src_path ()); path bf (src_base / root.root_extra->buildfile_file); if (exists (bf)) { l5 ([&]{trace << "loading buildfile " << bf << " for " << pk;}); retest = source_once (root, base, bf, root); } else if (exists (src_base)) { e = dir::search_implied (base, pk, trace); retest = (e != nullptr); } } } } assert (t.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); if (e != nullptr && !e->implied) return e; } } fail << "no explicit target for " << pk << endf; } static bool dir_pattern (const target_type&, const scope&, string& v, optional&, const location&, bool r) { // Add/strip trailing directory separator unless already there. // bool d (path::traits_type::is_separator (v.back ())); if (r) { assert (d); v.resize (v.size () - 1); } else if (!d) { v += path::traits_type::directory_separator; return true; } return false; } const target_type dir::static_type { "dir", &alias::static_type, &target_factory, nullptr, // Extension not used. nullptr, &dir_pattern, nullptr, &dir_search, false }; const target_type fsdir::static_type { "fsdir", &target::static_type, &target_factory, nullptr, // Extension not used. nullptr, &dir_pattern, nullptr, &target_search, false }; static optional exe_target_extension (const target_key&, const scope&, const char* e, bool search) { // If we are searching for an executable that is not a target, then use // the build machine executable extension. Otherwise, if this is a target, // then we expect the rule to supply the target machine extension. But if // it doesn't, then fallback to no extension (e.g., a script). // return string (!search ? (e != nullptr ? e : "") : #ifdef _WIN32 "exe" #else "" #endif ); } #ifdef _WIN32 static bool exe_target_pattern (const target_type&, const scope&, string& v, optional& e, const location& l, bool r) { if (r) { assert (e); e = nullopt; } else { e = target::split_name (v, l); if (!e) { e = "exe"; return true; } } return false; } #endif const target_type exe::static_type { "exe", &file::static_type, &target_factory, nullptr, /* fixed_extension */ &exe_target_extension, #ifdef _WIN32 &exe_target_pattern, #else nullptr, #endif nullptr, &file_search, false }; static const char* buildfile_target_extension (const target_key& tk, const scope* root) { // If the name is the special 'buildfile', then there is no extension, // otherwise it is 'build' (or 'build2file' and 'build2' in the // alternative naming scheme). // Let's try hard not to need the root scope by trusting the extensions // we were given. // // BTW, one way to get rid of all this root scope complication is to // always require explicit extension specification for buildfiles. Since // they are hardly ever mentioned explicitly, this should probably be ok. // if (tk.ext) return tk.ext->c_str (); if (root == nullptr) { // The same login as in target::root_scope(). // // Note: we are guaranteed the scope is never NULL for prerequisites // (where out/dir could be relative and none of this will work). // // @@ CTX TODO #if 0 root = scopes.find (tk.out->empty () ? *tk.dir : *tk.out).root_scope (); #endif if (root == nullptr || root->root_extra == nullptr) fail << "unable to determine extension for buildfile target " << tk; } return *tk.name == root->root_extra->buildfile_file.string () ? "" : root->root_extra->build_ext.c_str (); } static bool buildfile_target_pattern (const target_type&, const scope& base, string& v, optional& e, const location& l, bool r) { if (r) { assert (e); e = nullopt; } else { e = target::split_name (v, l); if (!e) { const scope* root (base.root_scope ()); if (root == nullptr || root->root_extra == nullptr) fail (l) << "unable to determine extension for buildfile pattern"; if (v != root->root_extra->buildfile_file.string ()) { e = root->root_extra->build_ext; return true; } } } return false; } const target_type buildfile::static_type { "build", &file::static_type, &target_factory, &buildfile_target_extension, nullptr, /* default_extension */ &buildfile_target_pattern, nullptr, &file_search, false }; const target_type doc::static_type { "doc", &file::static_type, &target_factory, &target_extension_fix, // Same as file (no extension). nullptr, /* default_extension */ nullptr, /* pattern */ // Same as file. &target_print_1_ext_verb, // Same as file. &file_search, false }; 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_extension, // Should be specified explicitly. nullptr, /* default_extension */ nullptr, &target_print_1_ext_verb, // Print extension even at verbosity level 0. &file_search, false }; extern const char man1_ext[] = "1"; // VC14 rejects constexpr. const target_type man1::static_type { "man1", &man::static_type, &target_factory, &target_extension_fix, nullptr, /* default_extension */ &target_pattern_fix, &target_print_0_ext_verb, // Fixed extension, no use printing. &file_search, false }; static const char* manifest_target_extension (const target_key& tk, const scope*) { // If the name is special 'manifest', then there is no extension, // otherwise it is .manifest. // return *tk.name == "manifest" ? "" : "manifest"; } static bool manifest_target_pattern (const target_type&, const scope&, string& v, optional& e, const location& l, bool r) { if (r) { assert (e); e = nullopt; } else { e = target::split_name (v, l); if (!e && v != "manifest") { e = "manifest"; return true; } } return false; } const target_type manifest::static_type { "manifest", &doc::static_type, &target_factory, &manifest_target_extension, nullptr, /* default_extension */ &manifest_target_pattern, nullptr, &file_search, false }; }