aboutsummaryrefslogtreecommitdiff
path: root/build2/target.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'build2/target.cxx')
-rw-r--r--build2/target.cxx537
1 files changed, 537 insertions, 0 deletions
diff --git a/build2/target.cxx b/build2/target.cxx
new file mode 100644
index 0000000..3932466
--- /dev/null
+++ b/build2/target.cxx
@@ -0,0 +1,537 @@
+// file : build2/target.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/target>
+
+#include <cassert>
+
+#include <butl/filesystem>
+
+#include <build2/scope>
+#include <build2/search>
+#include <build2/algorithm>
+#include <build2/diagnostics>
+
+using namespace std;
+
+namespace build2
+{
+ // target_type
+ //
+ bool target_type::
+ is_a (const target_type& tt) const
+ {
+ for (const target_type* p (this); p != nullptr; p = p->base)
+ if (*p == tt)
+ return true;
+
+ return false;
+ }
+
+ // target_state
+ //
+ static const char* target_state_[] = {
+ "unknown", "unchanged", "postponed", "changed", "failed", "group"};
+
+ ostream&
+ operator<< (ostream& os, target_state ts)
+ {
+ return os << target_state_[static_cast<uint8_t> (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
+ //
+
+ void target::
+ recipe (action_type a, recipe_type r)
+ {
+ assert (a > action || !recipe_);
+
+ bool override (a == action && recipe_); // See action::operator<.
+
+ // Only noop_recipe can be overridden.
+ //
+ if (override)
+ {
+ recipe_function** f (recipe_.target<recipe_function*> ());
+ assert (f != nullptr && *f == &noop_action);
+ }
+
+ action = a;
+ recipe_ = std::move (r);
+
+ // Also reset the target state. If this is a noop recipe, then
+ // mark the target unchanged so that we don't waste time executing
+ // the recipe.
+ //
+ raw_state = target_state::unknown;
+
+ if (recipe_function** f = recipe_.target<recipe_function*> ())
+ {
+ if (*f == &noop_action)
+ raw_state = target_state::unchanged;
+ }
+
+ // This one is tricky: we don't want to reset the dependents count
+ // if we are merely overriding with a "stronger" recipe.
+ //
+ if (!override)
+ dependents = 0;
+ }
+
+ void target::
+ reset (action_type)
+ {
+ prerequisite_targets.clear ();
+ }
+
+ group_view target::
+ group_members (action_type) const
+ {
+ assert (false); // Not a group or doesn't expose its members.
+ return group_view {nullptr, 0};
+ }
+
+ scope& target::
+ base_scope () const
+ {
+ return scopes.find (dir);
+ }
+
+ scope& target::
+ root_scope () const
+ {
+ // This is tricky to cache so we do the lookup for now.
+ //
+ scope* r (scopes.find (dir).root_scope ());
+ assert (r != nullptr);
+ return *r;
+ }
+
+ lookup<const value> target::
+ operator[] (const variable& var) const
+ {
+ using result = lookup<const value>;
+
+ if (auto p = vars.find (var))
+ return result (p, &vars);
+
+ if (group != nullptr)
+ {
+ if (auto p = group->vars.find (var))
+ return result (p, &group->vars);
+ }
+
+ // We cannot simply delegate to scope's lookup() since we also need
+ // to check the group.
+ //
+ for (const scope* s (&base_scope ()); s != nullptr; )
+ {
+ if (!s->target_vars.empty ())
+ {
+ if (auto l = s->target_vars.lookup (type (), name, var))
+ return l;
+
+ if (group != nullptr)
+ {
+ if (auto l = s->target_vars.lookup (group->type (), group->name, var))
+ return l;
+ }
+ }
+
+ if (auto r = s->vars.find (var))
+ return result (r, &s->vars);
+
+ switch (var.visibility)
+ {
+ case variable_visibility::scope:
+ s = nullptr;
+ break;
+ case variable_visibility::project:
+ s = s->root () ? nullptr : s->parent_scope ();
+ break;
+ case variable_visibility::normal:
+ s = s->parent_scope ();
+ break;
+ }
+ }
+
+ return result ();
+ }
+
+ value& target::
+ append (const variable& var)
+ {
+ auto l (operator[] (var));
+
+ if (l && l.belongs (*this)) // Existing variable in this target.
+ return const_cast<value&> (*l);
+
+ value& r (assign (var));
+
+ if (l)
+ r = *l; // Copy value from the outer scope.
+
+ return r;
+ }
+
+ ostream&
+ operator<< (ostream& os, const target& t)
+ {
+ return os << target_key {&t.type (), &t.dir, &t.name, &t.ext};
+ }
+
+ // target_set
+ //
+ target_set targets;
+
+ auto target_set::
+ find (const target_key& k, tracer& trace) const -> iterator
+ {
+ iterator i (map_.find (k));
+
+ if (i != end ())
+ {
+ target& t (**i);
+
+ // Update the extension if the existing target has it unspecified.
+ //
+ const string* ext (*k.ext);
+ if (t.ext != ext)
+ {
+ level5 ([&]{
+ diag_record r (trace);
+ r << "assuming target " << t << " is the same as the one with ";
+ if (ext == nullptr)
+ r << "unspecified extension";
+ else if (ext->empty ())
+ r << "no extension";
+ else
+ r << "extension " << *ext;
+ });
+
+ if (ext != nullptr)
+ t.ext = ext;
+ }
+ }
+
+ return i;
+ }
+
+ pair<target&, bool> target_set::
+ insert (const target_type& tt,
+ dir_path dir,
+ string name,
+ const string* ext,
+ tracer& trace)
+ {
+ iterator i (find (target_key {&tt, &dir, &name, &ext}, trace));
+ bool r (i == end ());
+
+ if (r)
+ {
+ unique_ptr<target> pt (tt.factory (tt, move (dir), move (name), ext));
+ i = map_.emplace (
+ make_pair (target_key {&tt, &pt->dir, &pt->name, &pt->ext},
+ move (pt))).first;
+ }
+
+ return pair<target&, bool> (**i, r);
+ }
+
+ ostream&
+ operator<< (ostream& os, const target_key& k)
+ {
+ // If the name is empty, then we want to print the directory
+ // inside {}, e.g., dir{bar/}, not bar/dir{}.
+ //
+ bool n (!k.name->empty ());
+ string d (diag_relative (*k.dir, false));
+
+ if (n)
+ os << d;
+
+ os << k.type->name << '{';
+
+ if (n)
+ {
+ os << *k.name;
+
+ if (*k.ext != nullptr && !(*k.ext)->empty ())
+ os << '.' << **k.ext;
+ }
+ else
+ os << d;
+
+ os << '}';
+
+ return os;
+ }
+
+ // path_target
+ //
+ void path_target::
+ derive_path (const char* de, const char* np, const char* ns)
+ {
+ string n;
+
+ if (np != nullptr)
+ n += np;
+
+ n += name;
+
+ if (ns != nullptr)
+ n += ns;
+
+ // Update the extension.
+ //
+ // See also search_existing_file() if updating anything here.
+ //
+ if (ext == nullptr)
+ {
+ // If provided by the caller, then use that.
+ //
+ if (de != nullptr)
+ ext = &extension_pool.find (de);
+ //
+ // Otherwis see if the target type has function that will
+ // give us the default extension.
+ //
+ else if (auto f = type ().extension)
+ ext = &f (key (), base_scope ()); // Already from the pool.
+ else
+ fail << "no default extension for target " << *this;
+ }
+
+ // Add the extension.
+ //
+ if (!ext->empty ())
+ {
+ n += '.';
+ n += *ext;
+ }
+
+ path_type p (dir / path_type (move (n)));
+ const path_type& ep (path ());
+
+ if (ep.empty ())
+ path (p);
+ else if (p != ep)
+ fail << "path mismatch for target " << *this <<
+ info << "assigned '" << ep << "'" <<
+ info << "derived '" << p << "'";
+ }
+
+ // file_target
+ //
+ timestamp file::
+ load_mtime () const
+ {
+ const path_type& f (path ());
+ assert (!f.empty ());
+ return file_mtime (f);
+ }
+
+ // Search functions.
+ //
+
+ target*
+ search_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 (pk);
+ }
+
+ target*
+ search_file (const prerequisite_key& pk)
+ {
+ // First see if there is an existing target.
+ //
+ if (target* t = search_existing_target (pk))
+ return t;
+
+ // Then look for an existing file in this target-type-specific
+ // list of paths (@@ TODO: comes from the variable).
+ //
+ if (pk.tk.dir->relative ())
+ {
+ dir_paths sp;
+ sp.push_back (pk.scope->src_path ()); // src_base
+ return search_existing_file (pk, sp);
+ }
+ else
+ return nullptr;
+ }
+
+ static target*
+ search_alias (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.
+ //
+ target* t (search_existing_target (pk));
+
+ if (t == nullptr)
+ fail << "no explicit target for prerequisite " << pk;
+
+ return t;
+ }
+
+ // type info
+ //
+
+ const target_type target::static_type
+ {
+ "target",
+ nullptr,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ const target_type mtime_target::static_type
+ {
+ "mtime_target",
+ &target::static_type,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ const target_type path_target::static_type
+ {
+ "path_target",
+ &mtime_target::static_type,
+ nullptr,
+ nullptr,
+ &search_target,
+ false
+ };
+
+ template <typename T>
+ static target*
+ file_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ // The file target type doesn't imply any extension. So if one
+ // wasn't specified, set it to empty rather than unspecified.
+ // In other words, we always treat file{foo} as file{foo.}.
+ //
+ return new T (move (d),
+ move (n),
+ (e != nullptr ? e : &extension_pool.find ("")));
+ }
+
+ constexpr const char file_ext_var[] = "extension";
+ constexpr const char file_ext_def[] = "";
+
+ const target_type file::static_type
+ {
+ "file",
+ &path_target::static_type,
+ &file_factory<file>,
+ &target_extension_var<file_ext_var, file_ext_def>,
+ &search_file,
+ false
+ };
+
+ const target_type alias::static_type
+ {
+ "alias",
+ &target::static_type,
+ &target_factory<alias>,
+ nullptr, // Should never need.
+ &search_alias,
+ false
+ };
+
+ const target_type dir::static_type
+ {
+ "dir",
+ &alias::static_type,
+ &target_factory<dir>,
+ nullptr, // Should never need.
+ &search_alias,
+ false
+ };
+
+ const target_type fsdir::static_type
+ {
+ "fsdir",
+ &target::static_type,
+ &target_factory<fsdir>,
+ nullptr, // Should never need.
+ &search_target,
+ false
+ };
+
+ static const std::string&
+ buildfile_target_extension (const target_key& tk, scope&)
+ {
+ // If the name is special 'buildfile', then there is no extension,
+ // otherwise it is .build.
+ //
+ return extension_pool.find (*tk.name == "buildfile" ? "" : "build");
+ }
+
+ const target_type buildfile::static_type
+ {
+ "buildfile",
+ &file::static_type,
+ &file_factory<buildfile>,
+ &buildfile_target_extension,
+ &search_file,
+ false
+ };
+
+ const target_type doc::static_type
+ {
+ "doc",
+ &file::static_type,
+ &file_factory<doc>,
+ &target_extension_var<file_ext_var, file_ext_def>, // Same as file.
+ &search_file,
+ false
+ };
+
+ static target*
+ man_factory (const target_type&, dir_path d, string n, const string* e)
+ {
+ if (e == nullptr)
+ fail << "man target '" << n << "' must include extension (man section)";
+
+ return new man (move (d), move (n), e);
+ }
+
+ const target_type man::static_type
+ {
+ "man",
+ &doc::static_type,
+ &man_factory,
+ nullptr, // Should be specified explicitly.
+ &search_file,
+ false
+ };
+
+ constexpr const char man1_ext[] = "1";
+ const target_type man1::static_type
+ {
+ "man1",
+ &man::static_type,
+ &file_factory<man1>,
+ &target_extension_fix<man1_ext>,
+ &search_file,
+ false
+ };
+}