From 70317569c6dcd9809ed4a8c425777e653ec6ca08 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 1 May 2017 18:24:31 +0300 Subject: Add hxx extension for headers --- README-GIT | 6 +- build/root.build | 2 +- build2/.gitignore | 2 +- build2/algorithm | 437 ------- build2/algorithm.cxx | 22 +- build2/algorithm.hxx | 437 +++++++ build2/algorithm.ixx | 4 +- build2/b-options | 540 -------- build2/b-options.cxx | 4 +- build2/b-options.hxx | 540 ++++++++ build2/b.cli | 2 +- build2/b.cxx | 58 +- build2/bin/guess | 107 -- build2/bin/guess.cxx | 4 +- build2/bin/guess.hxx | 107 ++ build2/bin/init | 100 -- build2/bin/init.cxx | 18 +- build2/bin/init.hxx | 100 ++ build2/bin/rule | 46 - build2/bin/rule.cxx | 12 +- build2/bin/rule.hxx | 46 + build2/bin/target | 120 -- build2/bin/target.cxx | 2 +- build2/bin/target.hxx | 120 ++ build2/buildfile | 10 +- build2/c/init | 37 - build2/c/init.cxx | 14 +- build2/c/init.hxx | 37 + build2/c/target | 22 - build2/c/target.hxx | 22 + build2/cc/common | 289 ----- build2/cc/common.cxx | 18 +- build2/cc/common.hxx | 289 +++++ build2/cc/compile | 94 -- build2/cc/compile.cxx | 20 +- build2/cc/compile.hxx | 94 ++ build2/cc/gcc.cxx | 18 +- build2/cc/guess | 141 --- build2/cc/guess.cxx | 9 +- build2/cc/guess.hxx | 141 +++ build2/cc/init | 64 - build2/cc/init.cxx | 12 +- build2/cc/init.hxx | 64 + build2/cc/install | 48 - build2/cc/install.cxx | 10 +- build2/cc/install.hxx | 48 + build2/cc/link | 130 -- build2/cc/link.cxx | 26 +- build2/cc/link.hxx | 130 ++ build2/cc/module | 68 - build2/cc/module.cxx | 16 +- build2/cc/module.hxx | 68 + build2/cc/msvc.cxx | 20 +- build2/cc/pkgconfig.cxx | 20 +- build2/cc/target | 63 - build2/cc/target.cxx | 2 +- build2/cc/target.hxx | 63 + build2/cc/types | 35 - build2/cc/types.hxx | 35 + build2/cc/utility | 51 - build2/cc/utility.cxx | 8 +- build2/cc/utility.hxx | 51 + build2/cc/windows-manifest.cxx | 16 +- build2/cc/windows-rpath.cxx | 14 +- build2/cli/init | 37 - build2/cli/init.cxx | 18 +- build2/cli/init.hxx | 37 + build2/cli/rule | 39 - build2/cli/rule.cxx | 16 +- build2/cli/rule.hxx | 39 + build2/cli/target | 55 - build2/cli/target.cxx | 2 +- build2/cli/target.hxx | 55 + build2/config/init | 31 - build2/config/init.cxx | 24 +- build2/config/init.hxx | 31 + build2/config/module | 94 -- build2/config/module.cxx | 2 +- build2/config/module.hxx | 94 ++ build2/config/operation | 29 - build2/config/operation.cxx | 24 +- build2/config/operation.hxx | 29 + build2/config/utility | 152 --- build2/config/utility.cxx | 6 +- build2/config/utility.hxx | 152 +++ build2/config/utility.txx | 4 +- build2/context | 399 ------ build2/context.cxx | 22 +- build2/context.hxx | 399 ++++++ build2/cxx/init | 37 - build2/cxx/init.cxx | 14 +- build2/cxx/init.hxx | 37 + build2/cxx/target | 63 - build2/cxx/target.cxx | 2 +- build2/cxx/target.hxx | 63 + build2/depdb | 201 --- build2/depdb.cxx | 4 +- build2/depdb.hxx | 201 +++ build2/diagnostics | 351 ------ build2/diagnostics.cxx | 4 +- build2/diagnostics.hxx | 351 ++++++ build2/dist/init | 31 - build2/dist/init.cxx | 16 +- build2/dist/init.hxx | 31 + build2/dist/module | 65 - build2/dist/module.cxx | 2 +- build2/dist/module.hxx | 65 + build2/dist/operation | 21 - build2/dist/operation.cxx | 22 +- build2/dist/operation.hxx | 21 + build2/dist/rule | 40 - build2/dist/rule.cxx | 10 +- build2/dist/rule.hxx | 40 + build2/dump | 21 - build2/dump.cxx | 12 +- build2/dump.hxx | 21 + build2/file | 187 --- build2/file.cxx | 22 +- build2/file.hxx | 187 +++ build2/filesystem | 107 -- build2/filesystem.cxx | 4 +- build2/filesystem.hxx | 107 ++ build2/filesystem.txx | 4 +- build2/function | 699 ----------- build2/function.cxx | 2 +- build2/function.hxx | 699 +++++++++++ build2/functions-builtin.cxx | 4 +- build2/functions-path.cxx | 4 +- build2/functions-process-path.cxx | 4 +- build2/functions-string.cxx | 4 +- build2/functions-target-triplet.cxx | 4 +- build2/install/init | 31 - build2/install/init.cxx | 20 +- build2/install/init.hxx | 31 + build2/install/operation | 22 - build2/install/operation.cxx | 2 +- build2/install/operation.hxx | 22 + build2/install/rule | 101 -- build2/install/rule.cxx | 14 +- build2/install/rule.hxx | 101 ++ build2/install/utility | 57 - build2/install/utility.hxx | 57 + build2/lexer | 195 --- build2/lexer.cxx | 2 +- build2/lexer.hxx | 195 +++ build2/module | 114 -- build2/module.cxx | 8 +- build2/module.hxx | 114 ++ build2/name | 158 --- build2/name.cxx | 4 +- build2/name.hxx | 158 +++ build2/operation | 435 ------- build2/operation.cxx | 16 +- build2/operation.hxx | 435 +++++++ build2/parser | 593 --------- build2/parser.cxx | 24 +- build2/parser.hxx | 593 +++++++++ build2/pkgconfig/init | 37 - build2/pkgconfig/init.cxx | 12 +- build2/pkgconfig/init.hxx | 37 + build2/prerequisite | 166 --- build2/prerequisite.cxx | 10 +- build2/prerequisite.hxx | 166 +++ build2/regex | 57 - build2/regex.cxx | 2 +- build2/regex.hxx | 57 + build2/rule | 140 --- build2/rule-map | 121 -- build2/rule-map.hxx | 121 ++ build2/rule.cxx | 16 +- build2/rule.hxx | 140 +++ build2/scheduler | 593 --------- build2/scheduler.cxx | 2 +- build2/scheduler.hxx | 593 +++++++++ build2/scope | 375 ------ build2/scope.cxx | 6 +- build2/scope.hxx | 375 ++++++ build2/search | 36 - build2/search.cxx | 14 +- build2/search.hxx | 36 + build2/spec | 69 -- build2/spec.cxx | 6 +- build2/spec.hxx | 69 ++ build2/target | 1719 -------------------------- build2/target-key | 96 -- build2/target-key.hxx | 96 ++ build2/target-type | 138 --- build2/target-type.hxx | 138 +++ build2/target.cxx | 16 +- build2/target.hxx | 1719 ++++++++++++++++++++++++++ build2/target.ixx | 2 +- build2/target.txx | 8 +- build2/test/common | 52 - build2/test/common.cxx | 6 +- build2/test/common.hxx | 52 + build2/test/init | 31 - build2/test/init.cxx | 18 +- build2/test/init.hxx | 31 + build2/test/module | 26 - build2/test/module.hxx | 26 + build2/test/operation | 21 - build2/test/operation.cxx | 2 +- build2/test/operation.hxx | 21 + build2/test/rule | 52 - build2/test/rule.cxx | 20 +- build2/test/rule.hxx | 52 + build2/test/script/builtin | 75 -- build2/test/script/builtin.cxx | 12 +- build2/test/script/builtin.hxx | 75 ++ build2/test/script/lexer | 90 -- build2/test/script/lexer.cxx | 2 +- build2/test/script/lexer.hxx | 90 ++ build2/test/script/parser | 245 ---- build2/test/script/parser.cxx | 8 +- build2/test/script/parser.hxx | 245 ++++ build2/test/script/regex | 684 ---------- build2/test/script/regex.cxx | 2 +- build2/test/script/regex.hxx | 684 ++++++++++ build2/test/script/runner | 90 -- build2/test/script/runner.cxx | 18 +- build2/test/script/runner.hxx | 90 ++ build2/test/script/script | 552 --------- build2/test/script/script.cxx | 6 +- build2/test/script/script.hxx | 552 +++++++++ build2/test/script/token | 65 - build2/test/script/token.cxx | 2 +- build2/test/script/token.hxx | 65 + build2/test/target | 29 - build2/test/target.cxx | 2 +- build2/test/target.hxx | 29 + build2/token | 186 --- build2/token.cxx | 2 +- build2/token.hxx | 186 +++ build2/types | 249 ---- build2/types-parsers | 38 - build2/types-parsers.cxx | 4 +- build2/types-parsers.hxx | 38 + build2/types.hxx | 249 ++++ build2/utility | 435 ------- build2/utility.cxx | 10 +- build2/utility.hxx | 436 +++++++ build2/variable | 1328 -------------------- build2/variable.cxx | 6 +- build2/variable.hxx | 1328 ++++++++++++++++++++ build2/variable.txx | 2 +- build2/version-impl | 18 - build2/version-impl.in | 44 - build2/version.hxx.in | 44 + build2/version/init | 31 - build2/version/init.cxx | 22 +- build2/version/init.hxx | 31 + build2/version/module | 36 - build2/version/module.cxx | 2 +- build2/version/module.hxx | 36 + build2/version/rule | 53 - build2/version/rule.cxx | 24 +- build2/version/rule.hxx | 53 + build2/version/snapshot | 33 - build2/version/snapshot-git.cxx | 2 +- build2/version/snapshot.cxx | 4 +- build2/version/snapshot.hxx | 33 + old-tests/depdb/driver.cxx | 8 +- tests/.gitignore | 5 + tests/build/root.build | 2 +- tests/test/script/runner/driver.cxx | 8 +- unit-tests/.gitignore | 5 + unit-tests/function/driver.cxx | 16 +- unit-tests/lexer/driver.cxx | 8 +- unit-tests/scheduler/driver.cxx | 6 +- unit-tests/test/script/lexer/driver.cxx | 8 +- unit-tests/test/script/parser/directive.test | 4 +- unit-tests/test/script/parser/driver.cxx | 18 +- unit-tests/test/script/regex/driver.cxx | 2 +- 273 files changed, 15293 insertions(+), 15299 deletions(-) delete mode 100644 build2/algorithm create mode 100644 build2/algorithm.hxx delete mode 100644 build2/b-options create mode 100644 build2/b-options.hxx delete mode 100644 build2/bin/guess create mode 100644 build2/bin/guess.hxx delete mode 100644 build2/bin/init create mode 100644 build2/bin/init.hxx delete mode 100644 build2/bin/rule create mode 100644 build2/bin/rule.hxx delete mode 100644 build2/bin/target create mode 100644 build2/bin/target.hxx delete mode 100644 build2/c/init create mode 100644 build2/c/init.hxx delete mode 100644 build2/c/target create mode 100644 build2/c/target.hxx delete mode 100644 build2/cc/common create mode 100644 build2/cc/common.hxx delete mode 100644 build2/cc/compile create mode 100644 build2/cc/compile.hxx delete mode 100644 build2/cc/guess create mode 100644 build2/cc/guess.hxx delete mode 100644 build2/cc/init create mode 100644 build2/cc/init.hxx delete mode 100644 build2/cc/install create mode 100644 build2/cc/install.hxx delete mode 100644 build2/cc/link create mode 100644 build2/cc/link.hxx delete mode 100644 build2/cc/module create mode 100644 build2/cc/module.hxx delete mode 100644 build2/cc/target create mode 100644 build2/cc/target.hxx delete mode 100644 build2/cc/types create mode 100644 build2/cc/types.hxx delete mode 100644 build2/cc/utility create mode 100644 build2/cc/utility.hxx delete mode 100644 build2/cli/init create mode 100644 build2/cli/init.hxx delete mode 100644 build2/cli/rule create mode 100644 build2/cli/rule.hxx delete mode 100644 build2/cli/target create mode 100644 build2/cli/target.hxx delete mode 100644 build2/config/init create mode 100644 build2/config/init.hxx delete mode 100644 build2/config/module create mode 100644 build2/config/module.hxx delete mode 100644 build2/config/operation create mode 100644 build2/config/operation.hxx delete mode 100644 build2/config/utility create mode 100644 build2/config/utility.hxx delete mode 100644 build2/context create mode 100644 build2/context.hxx delete mode 100644 build2/cxx/init create mode 100644 build2/cxx/init.hxx delete mode 100644 build2/cxx/target create mode 100644 build2/cxx/target.hxx delete mode 100644 build2/depdb create mode 100644 build2/depdb.hxx delete mode 100644 build2/diagnostics create mode 100644 build2/diagnostics.hxx delete mode 100644 build2/dist/init create mode 100644 build2/dist/init.hxx delete mode 100644 build2/dist/module create mode 100644 build2/dist/module.hxx delete mode 100644 build2/dist/operation create mode 100644 build2/dist/operation.hxx delete mode 100644 build2/dist/rule create mode 100644 build2/dist/rule.hxx delete mode 100644 build2/dump create mode 100644 build2/dump.hxx delete mode 100644 build2/file create mode 100644 build2/file.hxx delete mode 100644 build2/filesystem create mode 100644 build2/filesystem.hxx delete mode 100644 build2/function create mode 100644 build2/function.hxx delete mode 100644 build2/install/init create mode 100644 build2/install/init.hxx delete mode 100644 build2/install/operation create mode 100644 build2/install/operation.hxx delete mode 100644 build2/install/rule create mode 100644 build2/install/rule.hxx delete mode 100644 build2/install/utility create mode 100644 build2/install/utility.hxx delete mode 100644 build2/lexer create mode 100644 build2/lexer.hxx delete mode 100644 build2/module create mode 100644 build2/module.hxx delete mode 100644 build2/name create mode 100644 build2/name.hxx delete mode 100644 build2/operation create mode 100644 build2/operation.hxx delete mode 100644 build2/parser create mode 100644 build2/parser.hxx delete mode 100644 build2/pkgconfig/init create mode 100644 build2/pkgconfig/init.hxx delete mode 100644 build2/prerequisite create mode 100644 build2/prerequisite.hxx delete mode 100644 build2/regex create mode 100644 build2/regex.hxx delete mode 100644 build2/rule delete mode 100644 build2/rule-map create mode 100644 build2/rule-map.hxx create mode 100644 build2/rule.hxx delete mode 100644 build2/scheduler create mode 100644 build2/scheduler.hxx delete mode 100644 build2/scope create mode 100644 build2/scope.hxx delete mode 100644 build2/search create mode 100644 build2/search.hxx delete mode 100644 build2/spec create mode 100644 build2/spec.hxx delete mode 100644 build2/target delete mode 100644 build2/target-key create mode 100644 build2/target-key.hxx delete mode 100644 build2/target-type create mode 100644 build2/target-type.hxx create mode 100644 build2/target.hxx delete mode 100644 build2/test/common create mode 100644 build2/test/common.hxx delete mode 100644 build2/test/init create mode 100644 build2/test/init.hxx delete mode 100644 build2/test/module create mode 100644 build2/test/module.hxx delete mode 100644 build2/test/operation create mode 100644 build2/test/operation.hxx delete mode 100644 build2/test/rule create mode 100644 build2/test/rule.hxx delete mode 100644 build2/test/script/builtin create mode 100644 build2/test/script/builtin.hxx delete mode 100644 build2/test/script/lexer create mode 100644 build2/test/script/lexer.hxx delete mode 100644 build2/test/script/parser create mode 100644 build2/test/script/parser.hxx delete mode 100644 build2/test/script/regex create mode 100644 build2/test/script/regex.hxx delete mode 100644 build2/test/script/runner create mode 100644 build2/test/script/runner.hxx delete mode 100644 build2/test/script/script create mode 100644 build2/test/script/script.hxx delete mode 100644 build2/test/script/token create mode 100644 build2/test/script/token.hxx delete mode 100644 build2/test/target create mode 100644 build2/test/target.hxx delete mode 100644 build2/token create mode 100644 build2/token.hxx delete mode 100644 build2/types delete mode 100644 build2/types-parsers create mode 100644 build2/types-parsers.hxx create mode 100644 build2/types.hxx delete mode 100644 build2/utility create mode 100644 build2/utility.hxx delete mode 100644 build2/variable create mode 100644 build2/variable.hxx delete mode 100644 build2/version-impl delete mode 100644 build2/version-impl.in create mode 100644 build2/version.hxx.in delete mode 100644 build2/version/init create mode 100644 build2/version/init.hxx delete mode 100644 build2/version/module create mode 100644 build2/version/module.hxx delete mode 100644 build2/version/rule create mode 100644 build2/version/rule.hxx delete mode 100644 build2/version/snapshot create mode 100644 build2/version/snapshot.hxx diff --git a/README-GIT b/README-GIT index 3748aba..0383ddd 100644 --- a/README-GIT +++ b/README-GIT @@ -10,7 +10,7 @@ Alternatively, if you have already cloned without --recursive, run: git submodule update --init -The checked out build2/version-impl will be overwritten during the build -process but these changes should be ignored. To do this automatically, run: +The checked out build2/version.hxx will be overwritten during the build process +but these changes should be ignored. To do this automatically, run: -git update-index --assume-unchanged build2/version-impl +git update-index --assume-unchanged build2/version.hxx diff --git a/build/root.build b/build/root.build index a7b0df3..85f69a1 100644 --- a/build/root.build +++ b/build/root.build @@ -6,7 +6,7 @@ cxx.std = latest using cxx -hxx{*}: extension = +hxx{*}: extension = hxx ixx{*}: extension = ixx txx{*}: extension = txx cxx{*}: extension = cxx diff --git a/build2/.gitignore b/build2/.gitignore index a82a338..eda023f 100644 --- a/build2/.gitignore +++ b/build2/.gitignore @@ -2,4 +2,4 @@ b b-boot #*-options #*-options.?xx -version-impl +version.hxx diff --git a/build2/algorithm b/build2/algorithm deleted file mode 100644 index 601d2ef..0000000 --- a/build2/algorithm +++ /dev/null @@ -1,437 +0,0 @@ -// file : build2/algorithm -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_ALGORITHM -#define BUILD2_ALGORITHM - -#include -#include - -#include -#include - -namespace build2 -{ - class scope; - class prerequisite; - class prerequisite_key; - - // The default prerequisite search implementation. It first calls the - // prerequisite-type-specific search function. If that doesn't yeld - // anything, it creates a new target. - // - const target& - search (const target&, const prerequisite&); - - // As above but specify the prerequisite to search as a key. - // - const target& - search (const target&, const prerequisite_key&); - - // Uniform search interface for prerequisite/prerequisite_member. - // - inline const target& - search (const target& t, const prerequisite_member& p) {return p.search (t);} - - // As above but override the target type. Useful for searching for - // target group members where we need to search for a different - // target type. - // - const target& - search (const target&, const target_type&, const prerequisite_key&); - - // As above but specify the prerequisite to search as individual key - // components. Scope can be NULL if the directory is absolute. - // - const target& - search (const target&, - const target_type& type, - const dir_path& dir, - const dir_path& out, - const string& name, - const string* ext = nullptr, // NULL means unspecified. - const scope* = nullptr, // NULL means dir is absolute. - const optional& proj = nullopt); - - // As above but specify the target type as template argument. - // - template - const T& - search (const target&, - const dir_path& dir, - const dir_path& out, - const string& name, - const string* ext = nullptr, - const scope* = nullptr); - - // Search for a target identified by the name. The semantics is "as if" we - // first created a prerequisite based on this name in exactly the same way - // as the parser would and then searched based on this prerequisite. - // - const target& - search (const target&, name, const scope&); - - // As above but only search for an already existing target. Unlike the above - // version, this one can be called during the execute phase. Return NULL for - // unknown target types. - // - const target* - search_existing (const name&, - const scope&, - const dir_path& out = dir_path ()); - - // Target match lock: a non-const target reference as well as the - // target::offset_* state that has already been "achieved". - // - struct target_lock - { - using target_type = build2::target; - - target_type* target = nullptr; - size_t offset = 0; - - explicit operator bool () const {return target != nullptr;} - - void unlock (); - target_type* release (); - - target_lock () = default; - - target_lock (target_lock&&); - target_lock& operator= (target_lock&&); - - // Implementation details. - // - target_lock (const target_lock&) = delete; - target_lock& operator= (const target_lock&) = delete; - - target_lock (target_type* t, size_t o): target (t), offset (o) {} - ~target_lock (); - }; - - // If the target is already applied (for this action ) or executed, then no - // lock is acquired. Otherwise, the target must not yet be matched for this - // action. - // - // @@ MT fuzzy: what if it is already in the desired state, why assert? - // Currently we only use it with match_recipe(). - // - target_lock - lock (action, const target&); - - // Match and apply a rule to the action/target with ambiguity detection. - // Increment the target's dependents count, which means that you should call - // this function with the intent to also call execute(). Return the target - // state translating target_state::failed to the failed exception unless - // instructed otherwise. - // - // The unmatch argument allows optimizations that avoid calling execute(). - // If it is unmatch::unchanged then only unmatch the target if it is known - // to be unchanged after match. If it is unmatch::safe, then unmatch the - // target if it is safe (this includes unchanged or if we know that someone - // else will execute this target). Return true if unmatch succeeded. Always - // throw if failed. - // - enum class unmatch {none, unchanged, safe}; - - target_state - match (action, const target&, bool fail = true); - - bool - match (action, const target&, unmatch); - - // Start asynchronous match. Return target_state::postponed if the - // asynchrounous operation has been started and target_state::busy if the - // target has already been busy. Regardless of the result, match() must be - // called in order to complete the operation (except target_state::failed). - // - // If fail is false, then return target_state::failed if the target match - // failed. Otherwise, throw the failed exception if keep_going is false and - // return target_state::failed otherwise. - // - target_state - match_async (action, const target&, - size_t start_count, atomic_count& task_count, - bool fail = true); - - // Match by specifying the recipe directly. The target must be locked. - // - void - match_recipe (target_lock&, recipe); - - // Match a "delegate rule" from withing another rules' apply() function - // avoiding recursive matches (thus the third argument). Return recipe and - // recipe action (if any). Unless fail is false, fail if not rule is found. - // Otherwise return empty recipe. Note that unlike match(), this function - // does not increment the dependents count. See also the companion - // execute_delegate(). - // - pair - match_delegate (action, target&, const rule&, bool fail = true); - - // The standard prerequisite search and match implementations. They call - // search() and then match() for each prerequisite in a loop omitting out of - // project prerequisites for the clean operation. If this target is a member - // of a group, then they first do this to the group's prerequisites. - // - void - match_prerequisites (action, target&); - - // If we are cleaning, this function doesn't go into group members, - // as an optimization (the group should clean everything up). - // - void - match_prerequisite_members (action, target&); - - // As above but omit prerequisites that are not in the specified scope. - // - void - match_prerequisites (action, target&, const scope&); - - void - match_prerequisite_members (action, target&, const scope&); - - // Match (already searched) members of a group or similar prerequisite-like - // dependencies. Similar in semantics to match_prerequisites(). - // - void - match_members (action, target&, const target*[], size_t); - - template - inline void - match_members (action a, target& t, const target* (&ts)[N]) - { - match_members (a, t, ts, N); - } - - inline void - match_members (action a, target& t, vector& ts, size_t start) - { - match_members (a, t, ts.data () + start, ts.size () - start); - } - - // Unless already available, match, and, if necessary, execute the group - // in order to obtain its members list. Note that even after that the - // member's list might still not be available (e.g., if some wildcard/ - // fallback rule matched). - // - group_view - resolve_group_members (action, const target&); - - // Inject dependency on the target's directory fsdir{}, unless it is in the - // src tree or is outside of any project (say, for example, an installation - // directory). If the parent argument is true, then inject the parent - // directory of a target that is itself a directory (name is empty). Return - // the injected target or NULL. Normally this function is called from the - // rule's apply() function. - // - const fsdir* - inject_fsdir (action, target&, bool parent = true); - - // Execute the action on target, assuming a rule has been matched and the - // recipe for this action has been set. This is the synchrounous executor - // implementation (but may still return target_state::busy if the target - // is already being executed). Decrements the dependents count. - // - // Note: does not translate target_state::failed to the failed exception. - // - target_state - execute (action, const target&); - - // As above but start asynchronous execution. Return target_state::unknown - // if the asynchrounous execution has been started and target_state::busy if - // the target has already been busy. - // - // If fail is false, then return target_state::failed if the target match - // failed. Otherwise, throw the failed exception if keep_going is false and - // return target_state::failed otherwise. - // - target_state - execute_async (action, const target&, - size_t start_count, atomic_count& task_count, - bool fail = true); - - // Execute the recipe obtained with match_delegate(). Note that the target's - // state is neither checked nor updated by this function. In other words, - // the appropriate usage is to call this function from another recipe and to - // factor the obtained state into the one returned. - // - target_state - execute_delegate (const recipe&, action, const target&); - - // A special version of the above that should be used for "direct" and "now" - // execution, that is, side-stepping the normal target-prerequisite - // relationship (so no dependents count is decremented) and execution order - // (so this function never returns the postponed target state). - // - // Note: waits for the completion if the target is busy and translates - // target_state::failed to the failed exception. - // - target_state - execute_direct (action, const target&); - - // The default prerequisite execute implementation. Call execute_async() on - // each non-ignored (non-NULL) prerequisite target in a loop and then wait - // for their completion. Return target_state::changed if any of them were - // changed and target_state::unchanged otherwise. If a prerequisite's - // execution is postponed, then set its pointer in prerequisite_targets to - // NULL (since its state cannot be queried MT-safely). - // - // Note that this function can be used as a recipe. - // - target_state - straight_execute_prerequisites (action, const target&); - - // As above but iterates over the prerequisites in reverse. - // - target_state - reverse_execute_prerequisites (action, const target&); - - // Call straight or reverse depending on the current mode. - // - target_state - execute_prerequisites (action, const target&); - - // A version of the above that also determines whether the action needs to - // be executed on the target based on the passed timestamp and filter. - // - // The filter is passed each prerequisite target and is expected to signal - // which ones should be used for timestamp comparison. If the filter is - // NULL, then all the prerequisites are used. - // - // Note that the return value is an optional target state. If the target - // needs updating, then the value absent. Otherwise it is the state that - // should be returned. This is used to handle the situation where some - // prerequisites were updated but no update of the target is necessary. In - // this case we still signal that the target was (conceptually, but not - // physically) changed. This is important both to propagate the fact that - // some work has been done and to also allow our dependents to detect this - // case if they are up to something tricky (like recursively linking liba{} - // prerequisites). - // - // Note that because we use mtime, this function should normally only be - // used in the perform_update action (which is straight). - // - using prerequisite_filter = function; - - optional - execute_prerequisites (action, const target&, - const timestamp&, - const prerequisite_filter& = nullptr); - - // Another version of the above that does two extra things for the caller: - // it determines whether the action needs to be executed on the target based - // on the passed timestamp and finds a prerequisite of the specified type - // (e.g., a source file). If there are multiple prerequisites of this type, - // then the first is returned (this can become important if additional - // prerequisites of the same type may get injected). - // - template - pair, const T&> - execute_prerequisites (action, const target&, - const timestamp&, - const prerequisite_filter& = nullptr); - - pair, const target&> - execute_prerequisites (const target_type&, - action, const target&, - const timestamp&, - const prerequisite_filter& = nullptr); - - template - pair, const T&> - execute_prerequisites (const target_type&, - action, const target&, - const timestamp&, - const prerequisite_filter& = nullptr); - - // Execute members of a group or similar prerequisite-like dependencies. - // Similar in semantics to execute_prerequisites(). - // - target_state - straight_execute_members (action, const target&, const target*[], size_t); - - target_state - reverse_execute_members (action, const target&, const target*[], size_t); - - // Call straight or reverse depending on the current mode. - // - target_state - execute_members (action, const target&, const target*[], size_t); - - template - inline target_state - straight_execute_members (action a, const target& t, const target* (&ts)[N]) - { - return straight_execute_members (a, t, ts, N); - } - - template - inline target_state - reverse_execute_members (action a, const target& t, const target* (&ts)[N]) - { - return reverse_execute_members (a, t, ts, N); - } - - template - inline target_state - execute_members (action a, const target& t, const target* (&ts)[N]) - { - return execute_members (a, t, ts, N); - } - - // Return noop_recipe instead of using this function directly. - // - target_state - noop_action (action, const target&); - - // Default action implementation which forwards to the prerequisites. - // Use default_recipe instead of using this function directly. - // - target_state - default_action (action, const target&); - - // Standard perform(clean) action implementation for the file target - // (or derived). - // - target_state - perform_clean (action, const target&); - - // As above, but also removes the auxiliary dependency database (.d file). - // - target_state - perform_clean_depdb (action, const target&); - - // Helper for custom perform(clean) implementations that cleans extra files - // and directories (recursively) specified as a list of either absolute - // paths or "path derivation directives". The directive string can be NULL, - // or empty in which case it is ignored. If the last character in a - // directive is '/', then the resulting path is treated as a directory - // rather than a file. The directive can start with zero or more '-' - // characters which indicate the number of extensions that should be - // stripped before the new extension (if any) is added (so if you want to - // strip the extension, specify just "-"). For example: - // - // clean_extra (a, t, {".d", ".dlls/", "-.dll"}); - // - // The extra files/directories are removed first in the specified order - // followed by the ad hoc group member, then target itself, and, finally, - // the prerequisites in the reverse order. - // - // You can also clean extra files derived from adhoc group members. - // - target_state - clean_extra (action, const file&, - initializer_list> extra); - - inline target_state - clean_extra (action a, const file& f, initializer_list extra) - { - return clean_extra (a, f, {extra}); - } -} - -#include - -#endif // BUILD2_ALGORITHM diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index c6ff1b5..8203b08 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -2,17 +2,17 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#include -#include -#include -#include // import() -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include // import() +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx new file mode 100644 index 0000000..36e5a92 --- /dev/null +++ b/build2/algorithm.hxx @@ -0,0 +1,437 @@ +// file : build2/algorithm.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_ALGORITHM_HXX +#define BUILD2_ALGORITHM_HXX + +#include +#include + +#include +#include + +namespace build2 +{ + class scope; + class prerequisite; + class prerequisite_key; + + // The default prerequisite search implementation. It first calls the + // prerequisite-type-specific search function. If that doesn't yeld + // anything, it creates a new target. + // + const target& + search (const target&, const prerequisite&); + + // As above but specify the prerequisite to search as a key. + // + const target& + search (const target&, const prerequisite_key&); + + // Uniform search interface for prerequisite/prerequisite_member. + // + inline const target& + search (const target& t, const prerequisite_member& p) {return p.search (t);} + + // As above but override the target type. Useful for searching for + // target group members where we need to search for a different + // target type. + // + const target& + search (const target&, const target_type&, const prerequisite_key&); + + // As above but specify the prerequisite to search as individual key + // components. Scope can be NULL if the directory is absolute. + // + const target& + search (const target&, + const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const string* ext = nullptr, // NULL means unspecified. + const scope* = nullptr, // NULL means dir is absolute. + const optional& proj = nullopt); + + // As above but specify the target type as template argument. + // + template + const T& + search (const target&, + const dir_path& dir, + const dir_path& out, + const string& name, + const string* ext = nullptr, + const scope* = nullptr); + + // Search for a target identified by the name. The semantics is "as if" we + // first created a prerequisite based on this name in exactly the same way + // as the parser would and then searched based on this prerequisite. + // + const target& + search (const target&, name, const scope&); + + // As above but only search for an already existing target. Unlike the above + // version, this one can be called during the execute phase. Return NULL for + // unknown target types. + // + const target* + search_existing (const name&, + const scope&, + const dir_path& out = dir_path ()); + + // Target match lock: a non-const target reference as well as the + // target::offset_* state that has already been "achieved". + // + struct target_lock + { + using target_type = build2::target; + + target_type* target = nullptr; + size_t offset = 0; + + explicit operator bool () const {return target != nullptr;} + + void unlock (); + target_type* release (); + + target_lock () = default; + + target_lock (target_lock&&); + target_lock& operator= (target_lock&&); + + // Implementation details. + // + target_lock (const target_lock&) = delete; + target_lock& operator= (const target_lock&) = delete; + + target_lock (target_type* t, size_t o): target (t), offset (o) {} + ~target_lock (); + }; + + // If the target is already applied (for this action ) or executed, then no + // lock is acquired. Otherwise, the target must not yet be matched for this + // action. + // + // @@ MT fuzzy: what if it is already in the desired state, why assert? + // Currently we only use it with match_recipe(). + // + target_lock + lock (action, const target&); + + // Match and apply a rule to the action/target with ambiguity detection. + // Increment the target's dependents count, which means that you should call + // this function with the intent to also call execute(). Return the target + // state translating target_state::failed to the failed exception unless + // instructed otherwise. + // + // The unmatch argument allows optimizations that avoid calling execute(). + // If it is unmatch::unchanged then only unmatch the target if it is known + // to be unchanged after match. If it is unmatch::safe, then unmatch the + // target if it is safe (this includes unchanged or if we know that someone + // else will execute this target). Return true if unmatch succeeded. Always + // throw if failed. + // + enum class unmatch {none, unchanged, safe}; + + target_state + match (action, const target&, bool fail = true); + + bool + match (action, const target&, unmatch); + + // Start asynchronous match. Return target_state::postponed if the + // asynchrounous operation has been started and target_state::busy if the + // target has already been busy. Regardless of the result, match() must be + // called in order to complete the operation (except target_state::failed). + // + // If fail is false, then return target_state::failed if the target match + // failed. Otherwise, throw the failed exception if keep_going is false and + // return target_state::failed otherwise. + // + target_state + match_async (action, const target&, + size_t start_count, atomic_count& task_count, + bool fail = true); + + // Match by specifying the recipe directly. The target must be locked. + // + void + match_recipe (target_lock&, recipe); + + // Match a "delegate rule" from withing another rules' apply() function + // avoiding recursive matches (thus the third argument). Return recipe and + // recipe action (if any). Unless fail is false, fail if not rule is found. + // Otherwise return empty recipe. Note that unlike match(), this function + // does not increment the dependents count. See also the companion + // execute_delegate(). + // + pair + match_delegate (action, target&, const rule&, bool fail = true); + + // The standard prerequisite search and match implementations. They call + // search() and then match() for each prerequisite in a loop omitting out of + // project prerequisites for the clean operation. If this target is a member + // of a group, then they first do this to the group's prerequisites. + // + void + match_prerequisites (action, target&); + + // If we are cleaning, this function doesn't go into group members, + // as an optimization (the group should clean everything up). + // + void + match_prerequisite_members (action, target&); + + // As above but omit prerequisites that are not in the specified scope. + // + void + match_prerequisites (action, target&, const scope&); + + void + match_prerequisite_members (action, target&, const scope&); + + // Match (already searched) members of a group or similar prerequisite-like + // dependencies. Similar in semantics to match_prerequisites(). + // + void + match_members (action, target&, const target*[], size_t); + + template + inline void + match_members (action a, target& t, const target* (&ts)[N]) + { + match_members (a, t, ts, N); + } + + inline void + match_members (action a, target& t, vector& ts, size_t start) + { + match_members (a, t, ts.data () + start, ts.size () - start); + } + + // Unless already available, match, and, if necessary, execute the group + // in order to obtain its members list. Note that even after that the + // member's list might still not be available (e.g., if some wildcard/ + // fallback rule matched). + // + group_view + resolve_group_members (action, const target&); + + // Inject dependency on the target's directory fsdir{}, unless it is in the + // src tree or is outside of any project (say, for example, an installation + // directory). If the parent argument is true, then inject the parent + // directory of a target that is itself a directory (name is empty). Return + // the injected target or NULL. Normally this function is called from the + // rule's apply() function. + // + const fsdir* + inject_fsdir (action, target&, bool parent = true); + + // Execute the action on target, assuming a rule has been matched and the + // recipe for this action has been set. This is the synchrounous executor + // implementation (but may still return target_state::busy if the target + // is already being executed). Decrements the dependents count. + // + // Note: does not translate target_state::failed to the failed exception. + // + target_state + execute (action, const target&); + + // As above but start asynchronous execution. Return target_state::unknown + // if the asynchrounous execution has been started and target_state::busy if + // the target has already been busy. + // + // If fail is false, then return target_state::failed if the target match + // failed. Otherwise, throw the failed exception if keep_going is false and + // return target_state::failed otherwise. + // + target_state + execute_async (action, const target&, + size_t start_count, atomic_count& task_count, + bool fail = true); + + // Execute the recipe obtained with match_delegate(). Note that the target's + // state is neither checked nor updated by this function. In other words, + // the appropriate usage is to call this function from another recipe and to + // factor the obtained state into the one returned. + // + target_state + execute_delegate (const recipe&, action, const target&); + + // A special version of the above that should be used for "direct" and "now" + // execution, that is, side-stepping the normal target-prerequisite + // relationship (so no dependents count is decremented) and execution order + // (so this function never returns the postponed target state). + // + // Note: waits for the completion if the target is busy and translates + // target_state::failed to the failed exception. + // + target_state + execute_direct (action, const target&); + + // The default prerequisite execute implementation. Call execute_async() on + // each non-ignored (non-NULL) prerequisite target in a loop and then wait + // for their completion. Return target_state::changed if any of them were + // changed and target_state::unchanged otherwise. If a prerequisite's + // execution is postponed, then set its pointer in prerequisite_targets to + // NULL (since its state cannot be queried MT-safely). + // + // Note that this function can be used as a recipe. + // + target_state + straight_execute_prerequisites (action, const target&); + + // As above but iterates over the prerequisites in reverse. + // + target_state + reverse_execute_prerequisites (action, const target&); + + // Call straight or reverse depending on the current mode. + // + target_state + execute_prerequisites (action, const target&); + + // A version of the above that also determines whether the action needs to + // be executed on the target based on the passed timestamp and filter. + // + // The filter is passed each prerequisite target and is expected to signal + // which ones should be used for timestamp comparison. If the filter is + // NULL, then all the prerequisites are used. + // + // Note that the return value is an optional target state. If the target + // needs updating, then the value absent. Otherwise it is the state that + // should be returned. This is used to handle the situation where some + // prerequisites were updated but no update of the target is necessary. In + // this case we still signal that the target was (conceptually, but not + // physically) changed. This is important both to propagate the fact that + // some work has been done and to also allow our dependents to detect this + // case if they are up to something tricky (like recursively linking liba{} + // prerequisites). + // + // Note that because we use mtime, this function should normally only be + // used in the perform_update action (which is straight). + // + using prerequisite_filter = function; + + optional + execute_prerequisites (action, const target&, + const timestamp&, + const prerequisite_filter& = nullptr); + + // Another version of the above that does two extra things for the caller: + // it determines whether the action needs to be executed on the target based + // on the passed timestamp and finds a prerequisite of the specified type + // (e.g., a source file). If there are multiple prerequisites of this type, + // then the first is returned (this can become important if additional + // prerequisites of the same type may get injected). + // + template + pair, const T&> + execute_prerequisites (action, const target&, + const timestamp&, + const prerequisite_filter& = nullptr); + + pair, const target&> + execute_prerequisites (const target_type&, + action, const target&, + const timestamp&, + const prerequisite_filter& = nullptr); + + template + pair, const T&> + execute_prerequisites (const target_type&, + action, const target&, + const timestamp&, + const prerequisite_filter& = nullptr); + + // Execute members of a group or similar prerequisite-like dependencies. + // Similar in semantics to execute_prerequisites(). + // + target_state + straight_execute_members (action, const target&, const target*[], size_t); + + target_state + reverse_execute_members (action, const target&, const target*[], size_t); + + // Call straight or reverse depending on the current mode. + // + target_state + execute_members (action, const target&, const target*[], size_t); + + template + inline target_state + straight_execute_members (action a, const target& t, const target* (&ts)[N]) + { + return straight_execute_members (a, t, ts, N); + } + + template + inline target_state + reverse_execute_members (action a, const target& t, const target* (&ts)[N]) + { + return reverse_execute_members (a, t, ts, N); + } + + template + inline target_state + execute_members (action a, const target& t, const target* (&ts)[N]) + { + return execute_members (a, t, ts, N); + } + + // Return noop_recipe instead of using this function directly. + // + target_state + noop_action (action, const target&); + + // Default action implementation which forwards to the prerequisites. + // Use default_recipe instead of using this function directly. + // + target_state + default_action (action, const target&); + + // Standard perform(clean) action implementation for the file target + // (or derived). + // + target_state + perform_clean (action, const target&); + + // As above, but also removes the auxiliary dependency database (.d file). + // + target_state + perform_clean_depdb (action, const target&); + + // Helper for custom perform(clean) implementations that cleans extra files + // and directories (recursively) specified as a list of either absolute + // paths or "path derivation directives". The directive string can be NULL, + // or empty in which case it is ignored. If the last character in a + // directive is '/', then the resulting path is treated as a directory + // rather than a file. The directive can start with zero or more '-' + // characters which indicate the number of extensions that should be + // stripped before the new extension (if any) is added (so if you want to + // strip the extension, specify just "-"). For example: + // + // clean_extra (a, t, {".d", ".dlls/", "-.dll"}); + // + // The extra files/directories are removed first in the specified order + // followed by the ad hoc group member, then target itself, and, finally, + // the prerequisites in the reverse order. + // + // You can also clean extra files derived from adhoc group members. + // + target_state + clean_extra (action, const file&, + initializer_list> extra); + + inline target_state + clean_extra (action a, const file& f, initializer_list extra) + { + return clean_extra (a, f, {extra}); + } +} + +#include + +#endif // BUILD2_ALGORITHM_HXX diff --git a/build2/algorithm.ixx b/build2/algorithm.ixx index 46dece4..0e721f5 100644 --- a/build2/algorithm.ixx +++ b/build2/algorithm.ixx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include namespace build2 { diff --git a/build2/b-options b/build2/b-options deleted file mode 100644 index e674798..0000000 --- a/build2/b-options +++ /dev/null @@ -1,540 +0,0 @@ -// -*- C++ -*- -// -// This file was generated by CLI, a command line interface -// compiler for C++. -// - -#ifndef BUILD2_B_OPTIONS -#define BUILD2_B_OPTIONS - -// Begin prologue. -// -// -// End prologue. - -#include -#include -#include -#include -#include - -#ifndef CLI_POTENTIALLY_UNUSED -# if defined(_MSC_VER) || defined(__xlC__) -# define CLI_POTENTIALLY_UNUSED(x) (void*)&x -# else -# define CLI_POTENTIALLY_UNUSED(x) (void)x -# endif -#endif - -namespace build2 -{ - namespace cl - { - class usage_para - { - public: - enum value - { - none, - text, - option - }; - - usage_para (value); - - operator value () const - { - return v_; - } - - private: - value v_; - }; - - class unknown_mode - { - public: - enum value - { - skip, - stop, - fail - }; - - unknown_mode (value); - - operator value () const - { - return v_; - } - - private: - value v_; - }; - - // Exceptions. - // - - class exception: public std::exception - { - public: - virtual void - print (::std::ostream&) const = 0; - }; - - ::std::ostream& - operator<< (::std::ostream&, const exception&); - - class unknown_option: public exception - { - public: - virtual - ~unknown_option () throw (); - - unknown_option (const std::string& option); - - const std::string& - option () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - }; - - class unknown_argument: public exception - { - public: - virtual - ~unknown_argument () throw (); - - unknown_argument (const std::string& argument); - - const std::string& - argument () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string argument_; - }; - - class missing_value: public exception - { - public: - virtual - ~missing_value () throw (); - - missing_value (const std::string& option); - - const std::string& - option () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - }; - - class invalid_value: public exception - { - public: - virtual - ~invalid_value () throw (); - - invalid_value (const std::string& option, - const std::string& value); - - const std::string& - option () const; - - const std::string& - value () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string option_; - std::string value_; - }; - - class eos_reached: public exception - { - public: - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - }; - - class file_io_failure: public exception - { - public: - virtual - ~file_io_failure () throw (); - - file_io_failure (const std::string& file); - - const std::string& - file () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string file_; - }; - - class unmatched_quote: public exception - { - public: - virtual - ~unmatched_quote () throw (); - - unmatched_quote (const std::string& argument); - - const std::string& - argument () const; - - virtual void - print (::std::ostream&) const; - - virtual const char* - what () const throw (); - - private: - std::string argument_; - }; - - class scanner - { - public: - virtual - ~scanner (); - - virtual bool - more () = 0; - - virtual const char* - peek () = 0; - - virtual const char* - next () = 0; - - virtual void - skip () = 0; - }; - - class argv_scanner: public scanner - { - public: - argv_scanner (int& argc, char** argv, bool erase = false); - argv_scanner (int start, int& argc, char** argv, bool erase = false); - - int - end () const; - - virtual bool - more (); - - virtual const char* - peek (); - - virtual const char* - next (); - - virtual void - skip (); - - private: - int i_; - int& argc_; - char** argv_; - bool erase_; - }; - - class argv_file_scanner: public argv_scanner - { - public: - argv_file_scanner (int& argc, - char** argv, - const std::string& option, - bool erase = false); - - argv_file_scanner (int start, - int& argc, - char** argv, - const std::string& option, - bool erase = false); - - struct option_info - { - // If search_func is not NULL, it is called, with the arg - // value as the second argument, to locate the options file. - // If it returns an empty string, then the file is ignored. - // - const char* option; - std::string (*search_func) (const char*, void* arg); - void* arg; - }; - - argv_file_scanner (int& argc, - char** argv, - const option_info* options, - std::size_t options_count, - bool erase = false); - - argv_file_scanner (int start, - int& argc, - char** argv, - const option_info* options, - std::size_t options_count, - bool erase = false); - - virtual bool - more (); - - virtual const char* - peek (); - - virtual const char* - next (); - - virtual void - skip (); - - private: - const option_info* - find (const char*) const; - - void - load (const std::string& file); - - typedef argv_scanner base; - - const std::string option_; - option_info option_info_; - const option_info* options_; - std::size_t options_count_; - - std::string hold_; - std::deque args_; - bool skip_; - }; - - template - struct parser; - } -} - -#include - -namespace build2 -{ - class options - { - public: - options (); - - void - parse (int& argc, - char** argv, - bool erase = false, - ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, - ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); - - void - parse (int start, - int& argc, - char** argv, - bool erase = false, - ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, - ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); - - void - parse (int& argc, - char** argv, - int& end, - bool erase = false, - ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, - ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); - - void - parse (int start, - int& argc, - char** argv, - int& end, - bool erase = false, - ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, - ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); - - void - parse (::build2::cl::scanner&, - ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, - ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); - - // Option accessors. - // - const bool& - v () const; - - const bool& - V () const; - - const bool& - quiet () const; - - const uint16_t& - verbose () const; - - bool - verbose_specified () const; - - const size_t& - jobs () const; - - bool - jobs_specified () const; - - const size_t& - max_jobs () const; - - bool - max_jobs_specified () const; - - const size_t& - queue_depth () const; - - bool - queue_depth_specified () const; - - const bool& - serial_stop () const; - - const bool& - no_column () const; - - const bool& - no_line () const; - - const path& - buildfile () const; - - bool - buildfile_specified () const; - - const path& - config_guess () const; - - bool - config_guess_specified () const; - - const path& - config_sub () const; - - bool - config_sub_specified () const; - - const string& - pager () const; - - bool - pager_specified () const; - - const strings& - pager_option () const; - - bool - pager_option_specified () const; - - const bool& - help () const; - - const bool& - version () const; - - // Print usage information. - // - static ::build2::cl::usage_para - print_usage (::std::ostream&, - ::build2::cl::usage_para = ::build2::cl::usage_para::none); - - // Implementation details. - // - protected: - bool - _parse (const char*, ::build2::cl::scanner&); - - private: - void - _parse (::build2::cl::scanner&, - ::build2::cl::unknown_mode option, - ::build2::cl::unknown_mode argument); - - public: - bool v_; - bool V_; - bool quiet_; - uint16_t verbose_; - bool verbose_specified_; - size_t jobs_; - bool jobs_specified_; - size_t max_jobs_; - bool max_jobs_specified_; - size_t queue_depth_; - bool queue_depth_specified_; - bool serial_stop_; - bool no_column_; - bool no_line_; - path buildfile_; - bool buildfile_specified_; - path config_guess_; - bool config_guess_specified_; - path config_sub_; - bool config_sub_specified_; - string pager_; - bool pager_specified_; - strings pager_option_; - bool pager_option_specified_; - bool help_; - bool version_; - }; -} - -// Print page usage information. -// -namespace build2 -{ - ::build2::cl::usage_para - print_b_usage (::std::ostream&, - ::build2::cl::usage_para = ::build2::cl::usage_para::none); -} - -#include - -// Begin epilogue. -// -// -// End epilogue. - -#endif // BUILD2_B_OPTIONS diff --git a/build2/b-options.cxx b/build2/b-options.cxx index d31868c..50871f0 100644 --- a/build2/b-options.cxx +++ b/build2/b-options.cxx @@ -6,11 +6,11 @@ // Begin prologue. // -#include +#include // // End prologue. -#include +#include #include #include diff --git a/build2/b-options.hxx b/build2/b-options.hxx new file mode 100644 index 0000000..a280a79 --- /dev/null +++ b/build2/b-options.hxx @@ -0,0 +1,540 @@ +// -*- C++ -*- +// +// This file was generated by CLI, a command line interface +// compiler for C++. +// + +#ifndef BUILD2_B_OPTIONS_HXX +#define BUILD2_B_OPTIONS_HXX + +// Begin prologue. +// +// +// End prologue. + +#include +#include +#include +#include +#include + +#ifndef CLI_POTENTIALLY_UNUSED +# if defined(_MSC_VER) || defined(__xlC__) +# define CLI_POTENTIALLY_UNUSED(x) (void*)&x +# else +# define CLI_POTENTIALLY_UNUSED(x) (void)x +# endif +#endif + +namespace build2 +{ + namespace cl + { + class usage_para + { + public: + enum value + { + none, + text, + option + }; + + usage_para (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + class unknown_mode + { + public: + enum value + { + skip, + stop, + fail + }; + + unknown_mode (value); + + operator value () const + { + return v_; + } + + private: + value v_; + }; + + // Exceptions. + // + + class exception: public std::exception + { + public: + virtual void + print (::std::ostream&) const = 0; + }; + + ::std::ostream& + operator<< (::std::ostream&, const exception&); + + class unknown_option: public exception + { + public: + virtual + ~unknown_option () throw (); + + unknown_option (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class unknown_argument: public exception + { + public: + virtual + ~unknown_argument () throw (); + + unknown_argument (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class missing_value: public exception + { + public: + virtual + ~missing_value () throw (); + + missing_value (const std::string& option); + + const std::string& + option () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + }; + + class invalid_value: public exception + { + public: + virtual + ~invalid_value () throw (); + + invalid_value (const std::string& option, + const std::string& value); + + const std::string& + option () const; + + const std::string& + value () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string option_; + std::string value_; + }; + + class eos_reached: public exception + { + public: + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + + class unmatched_quote: public exception + { + public: + virtual + ~unmatched_quote () throw (); + + unmatched_quote (const std::string& argument); + + const std::string& + argument () const; + + virtual void + print (::std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string argument_; + }; + + class scanner + { + public: + virtual + ~scanner (); + + virtual bool + more () = 0; + + virtual const char* + peek () = 0; + + virtual const char* + next () = 0; + + virtual void + skip () = 0; + }; + + class argv_scanner: public scanner + { + public: + argv_scanner (int& argc, char** argv, bool erase = false); + argv_scanner (int start, int& argc, char** argv, bool erase = false); + + int + end () const; + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + int i_; + int& argc_; + char** argv_; + bool erase_; + }; + + class argv_file_scanner: public argv_scanner + { + public: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase = false); + + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase = false); + + struct option_info + { + // If search_func is not NULL, it is called, with the arg + // value as the second argument, to locate the options file. + // If it returns an empty string, then the file is ignored. + // + const char* option; + std::string (*search_func) (const char*, void* arg); + void* arg; + }; + + argv_file_scanner (int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false); + + argv_file_scanner (int start, + int& argc, + char** argv, + const option_info* options, + std::size_t options_count, + bool erase = false); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + const option_info* + find (const char*) const; + + void + load (const std::string& file); + + typedef argv_scanner base; + + const std::string option_; + option_info option_info_; + const option_info* options_; + std::size_t options_count_; + + std::string hold_; + std::deque args_; + bool skip_; + }; + + template + struct parser; + } +} + +#include + +namespace build2 +{ + class options + { + public: + options (); + + void + parse (int& argc, + char** argv, + bool erase = false, + ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, + ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); + + void + parse (int start, + int& argc, + char** argv, + bool erase = false, + ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, + ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); + + void + parse (int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, + ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); + + void + parse (int start, + int& argc, + char** argv, + int& end, + bool erase = false, + ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, + ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); + + void + parse (::build2::cl::scanner&, + ::build2::cl::unknown_mode option = ::build2::cl::unknown_mode::fail, + ::build2::cl::unknown_mode argument = ::build2::cl::unknown_mode::stop); + + // Option accessors. + // + const bool& + v () const; + + const bool& + V () const; + + const bool& + quiet () const; + + const uint16_t& + verbose () const; + + bool + verbose_specified () const; + + const size_t& + jobs () const; + + bool + jobs_specified () const; + + const size_t& + max_jobs () const; + + bool + max_jobs_specified () const; + + const size_t& + queue_depth () const; + + bool + queue_depth_specified () const; + + const bool& + serial_stop () const; + + const bool& + no_column () const; + + const bool& + no_line () const; + + const path& + buildfile () const; + + bool + buildfile_specified () const; + + const path& + config_guess () const; + + bool + config_guess_specified () const; + + const path& + config_sub () const; + + bool + config_sub_specified () const; + + const string& + pager () const; + + bool + pager_specified () const; + + const strings& + pager_option () const; + + bool + pager_option_specified () const; + + const bool& + help () const; + + const bool& + version () const; + + // Print usage information. + // + static ::build2::cl::usage_para + print_usage (::std::ostream&, + ::build2::cl::usage_para = ::build2::cl::usage_para::none); + + // Implementation details. + // + protected: + bool + _parse (const char*, ::build2::cl::scanner&); + + private: + void + _parse (::build2::cl::scanner&, + ::build2::cl::unknown_mode option, + ::build2::cl::unknown_mode argument); + + public: + bool v_; + bool V_; + bool quiet_; + uint16_t verbose_; + bool verbose_specified_; + size_t jobs_; + bool jobs_specified_; + size_t max_jobs_; + bool max_jobs_specified_; + size_t queue_depth_; + bool queue_depth_specified_; + bool serial_stop_; + bool no_column_; + bool no_line_; + path buildfile_; + bool buildfile_specified_; + path config_guess_; + bool config_guess_specified_; + path config_sub_; + bool config_sub_specified_; + string pager_; + bool pager_specified_; + strings pager_option_; + bool pager_option_specified_; + bool help_; + bool version_; + }; +} + +// Print page usage information. +// +namespace build2 +{ + ::build2::cl::usage_para + print_b_usage (::std::ostream&, + ::build2::cl::usage_para = ::build2::cl::usage_para::none); +} + +#include + +// Begin epilogue. +// +// +// End epilogue. + +#endif // BUILD2_B_OPTIONS_HXX diff --git a/build2/b.cli b/build2/b.cli index 3d988a8..a6e1abc 100644 --- a/build2/b.cli +++ b/build2/b.cli @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -include ; +include ; "\section=1" "\name=b" diff --git a/build2/b.cxx b/build2/b.cxx index cafc18f..dbd002b 100644 --- a/build2/b.cxx +++ b/build2/b.cxx @@ -13,43 +13,43 @@ #include #include // cout -#include +#include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include -#include +#include using namespace butl; using namespace std; -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace build2 { diff --git a/build2/bin/guess b/build2/bin/guess deleted file mode 100644 index 659fcd0..0000000 --- a/build2/bin/guess +++ /dev/null @@ -1,107 +0,0 @@ -// file : build2/bin/guess -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_BIN_GUESS -#define BUILD2_BIN_GUESS - -#include -#include - -namespace build2 -{ - namespace bin - { - // ar/ranlib information. - // - // Currently recognized ar/ranlib and their ids: - // - // gnu GNU binutils - // llvm LLVM - // bsd FreeBSD (and maybe other BSDs) - // msvc Microsoft's lib.exe - // generic Generic/unrecognized - // - // The signature is normally the --version/-V line. - // - // The checksum is used to detect ar/ranlib changes. It is calculated in - // a toolchain-specific manner (usually the output of --version/-V) and - // is not bulletproof. - // - struct ar_info - { - process_path ar_path; - string ar_id; - string ar_signature; - string ar_checksum; - - process_path ranlib_path; - string ranlib_id; - string ranlib_signature; - string ranlib_checksum; - }; - - // The ranlib path can be NULL, in which case no ranlib guessing will be - // attemplated and the returned ranlib_* members will be left empty. - // - ar_info - guess_ar (const path& ar, const path* ranlib, const dir_path& fallback); - - // ld information. - // - // Currently recognized linkers and their ids: - // - // gnu GNU binutils ld.bfd - // gold GNU binutils ld.gold - // llvm LLVM lld (note: not llvm-ld or llvm-link) - // ld64 Apple's new linker - // cctools Apple's old/classic linker - // msvc Microsoft's link.exe - // - // Note that BSDs are currently using GNU ld but some of them (e.g., - // FreeBSD) are hoping to migrate to lld. - // - // The signature is normally the --version/-version/-v line. - // - // The checksum is used to detect ld changes. It is calculated in a - // toolchain-specific manner (usually the output of --version/-version/-v) - // and is not bulletproof. - // - struct ld_info - { - process_path path; - string id; - string signature; - string checksum; - }; - - ld_info - guess_ld (const path& ld, const dir_path& fallback); - - // rc information. - // - // Currently recognized resource compilers and their ids: - // - // gnu GNU binutils windres - // msvc Microsoft's rc.exe - // - // The signature is normally the --version line. - // - // The checksum is used to detect rc changes. It is calculated in a - // toolchain-specific manner (usually the output of --version) and is not - // bulletproof. - // - struct rc_info - { - process_path path; - string id; - string signature; - string checksum; - }; - - rc_info - guess_rc (const path& rc, const dir_path& fallback); - } -} - -#endif // BUILD2_BIN_GUESS diff --git a/build2/bin/guess.cxx b/build2/bin/guess.cxx index 90012d4..780ccf8 100644 --- a/build2/bin/guess.cxx +++ b/build2/bin/guess.cxx @@ -2,9 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include +#include using namespace std; diff --git a/build2/bin/guess.hxx b/build2/bin/guess.hxx new file mode 100644 index 0000000..803f299 --- /dev/null +++ b/build2/bin/guess.hxx @@ -0,0 +1,107 @@ +// file : build2/bin/guess.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_GUESS_HXX +#define BUILD2_BIN_GUESS_HXX + +#include +#include + +namespace build2 +{ + namespace bin + { + // ar/ranlib information. + // + // Currently recognized ar/ranlib and their ids: + // + // gnu GNU binutils + // llvm LLVM + // bsd FreeBSD (and maybe other BSDs) + // msvc Microsoft's lib.exe + // generic Generic/unrecognized + // + // The signature is normally the --version/-V line. + // + // The checksum is used to detect ar/ranlib changes. It is calculated in + // a toolchain-specific manner (usually the output of --version/-V) and + // is not bulletproof. + // + struct ar_info + { + process_path ar_path; + string ar_id; + string ar_signature; + string ar_checksum; + + process_path ranlib_path; + string ranlib_id; + string ranlib_signature; + string ranlib_checksum; + }; + + // The ranlib path can be NULL, in which case no ranlib guessing will be + // attemplated and the returned ranlib_* members will be left empty. + // + ar_info + guess_ar (const path& ar, const path* ranlib, const dir_path& fallback); + + // ld information. + // + // Currently recognized linkers and their ids: + // + // gnu GNU binutils ld.bfd + // gold GNU binutils ld.gold + // llvm LLVM lld (note: not llvm-ld or llvm-link) + // ld64 Apple's new linker + // cctools Apple's old/classic linker + // msvc Microsoft's link.exe + // + // Note that BSDs are currently using GNU ld but some of them (e.g., + // FreeBSD) are hoping to migrate to lld. + // + // The signature is normally the --version/-version/-v line. + // + // The checksum is used to detect ld changes. It is calculated in a + // toolchain-specific manner (usually the output of --version/-version/-v) + // and is not bulletproof. + // + struct ld_info + { + process_path path; + string id; + string signature; + string checksum; + }; + + ld_info + guess_ld (const path& ld, const dir_path& fallback); + + // rc information. + // + // Currently recognized resource compilers and their ids: + // + // gnu GNU binutils windres + // msvc Microsoft's rc.exe + // + // The signature is normally the --version line. + // + // The checksum is used to detect rc changes. It is calculated in a + // toolchain-specific manner (usually the output of --version) and is not + // bulletproof. + // + struct rc_info + { + process_path path; + string id; + string signature; + string checksum; + }; + + rc_info + guess_rc (const path& rc, const dir_path& fallback); + } +} + +#endif // BUILD2_BIN_GUESS_HXX diff --git a/build2/bin/init b/build2/bin/init deleted file mode 100644 index 430d789..0000000 --- a/build2/bin/init +++ /dev/null @@ -1,100 +0,0 @@ -// file : build2/bin/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_BIN_INIT -#define BUILD2_BIN_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace bin - { - bool - vars_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - ar_config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - ar_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - ld_config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - ld_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - rc_config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - rc_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_BIN_INIT diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 9d08b62..71c77b2 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -2,20 +2,20 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include -#include -#include -#include +#include +#include +#include -#include -#include +#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/bin/init.hxx b/build2/bin/init.hxx new file mode 100644 index 0000000..b629f94 --- /dev/null +++ b/build2/bin/init.hxx @@ -0,0 +1,100 @@ +// file : build2/bin/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_INIT_HXX +#define BUILD2_BIN_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace bin + { + bool + vars_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + ar_config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + ar_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + ld_config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + ld_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + rc_config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + rc_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_BIN_INIT_HXX diff --git a/build2/bin/rule b/build2/bin/rule deleted file mode 100644 index 95747c2..0000000 --- a/build2/bin/rule +++ /dev/null @@ -1,46 +0,0 @@ -// file : build2/bin/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_BIN_RULE -#define BUILD2_BIN_RULE - -#include -#include - -#include - -namespace build2 -{ - namespace bin - { - class obj_rule: public rule - { - public: - obj_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - }; - - class lib_rule: public rule - { - public: - lib_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static target_state - perform (action, const target&); - }; - } -} - -#endif // BUILD2_BIN_RULE diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index fd1526e..3aa3c81 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include using namespace std; diff --git a/build2/bin/rule.hxx b/build2/bin/rule.hxx new file mode 100644 index 0000000..7109473 --- /dev/null +++ b/build2/bin/rule.hxx @@ -0,0 +1,46 @@ +// file : build2/bin/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_RULE_HXX +#define BUILD2_BIN_RULE_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace bin + { + class obj_rule: public rule + { + public: + obj_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + }; + + class lib_rule: public rule + { + public: + lib_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform (action, const target&); + }; + } +} + +#endif // BUILD2_BIN_RULE_HXX diff --git a/build2/bin/target b/build2/bin/target deleted file mode 100644 index 35bde60..0000000 --- a/build2/bin/target +++ /dev/null @@ -1,120 +0,0 @@ -// file : build2/bin/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_BIN_TARGET -#define BUILD2_BIN_TARGET - -#include -#include - -#include - -namespace build2 -{ - namespace bin - { - // The obj{} target group. - // - class obje: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class obja: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class objs: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class obj: public target - { - public: - using target::target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // The lib{} target group. - // - class liba: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class libs: public file - { - public: - using file::file; - - public: - static const target_type static_type; - - virtual const target_type& - dynamic_type () const override {return static_type;} - }; - - // Standard layout type compatible with group_view's const target*[2]. - // - struct lib_members - { - const liba* a = nullptr; - const libs* s = nullptr; - }; - - class lib: public target, public lib_members - { - public: - using target::target; - - virtual group_view - group_members (action_type) const override; - - public: - static const target_type static_type; - - virtual const target_type& - dynamic_type () const override {return static_type;} - }; - - // Windows import library. - // - class libi: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - } -} - -#endif // BUILD2_BIN_TARGET diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index dc691d6..962befa 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/bin/target.hxx b/build2/bin/target.hxx new file mode 100644 index 0000000..d920cc1 --- /dev/null +++ b/build2/bin/target.hxx @@ -0,0 +1,120 @@ +// file : build2/bin/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_BIN_TARGET_HXX +#define BUILD2_BIN_TARGET_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace bin + { + // The obj{} target group. + // + class obje: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class obja: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class objs: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class obj: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // The lib{} target group. + // + class liba: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class libs: public file + { + public: + using file::file; + + public: + static const target_type static_type; + + virtual const target_type& + dynamic_type () const override {return static_type;} + }; + + // Standard layout type compatible with group_view's const target*[2]. + // + struct lib_members + { + const liba* a = nullptr; + const libs* s = nullptr; + }; + + class lib: public target, public lib_members + { + public: + using target::target; + + virtual group_view + group_members (action_type) const override; + + public: + static const target_type static_type; + + virtual const target_type& + dynamic_type () const override {return static_type;} + }; + + // Windows import library. + // + class libi: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_BIN_TARGET_HXX diff --git a/build2/buildfile b/build2/buildfile index 8f8d51c..2d65001 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -41,7 +41,7 @@ exe{b}: \ {hxx cxx}{ types-parsers } \ {hxx ixx txx cxx}{ utility } \ {hxx ixx txx cxx}{ variable } \ - {hxx }{ version-impl } \ + {hxx }{ version } \ bin/{hxx cxx}{ guess } \ bin/{hxx cxx}{ init } \ bin/{hxx cxx}{ rule } \ @@ -107,8 +107,8 @@ exe{b}: {hxx ixx txx cxx}{** -b-options -dummy} \ liba{b} $libs #\ -hxx{version-impl}: in{version-impl} $src_root/file{manifest} -hxx{version-impl}: dist = true +hxx{version}: in{version} $src_root/file{manifest} +hxx{version}: dist = true # Fake utility library (without it code generation does not work). # @@ -130,8 +130,8 @@ if $cli.configured cli.cxx{b-options}: cli{b} cli.options += -I $src_root --include-with-brackets --include-prefix build2 \ ---guard-prefix BUILD2 --cxx-prologue "#include " \ ---cli-namespace build2::cl --generate-file-scanner --generate-parse \ +--guard-prefix BUILD2 --cxx-prologue "#include " \ +--cli-namespace build2::cl --generate-file-scanner --generate-parse \ --generate-specifier # Usage options. diff --git a/build2/c/init b/build2/c/init deleted file mode 100644 index 3219cd3..0000000 --- a/build2/c/init +++ /dev/null @@ -1,37 +0,0 @@ -// file : build2/c/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_C_INIT -#define BUILD2_C_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace c - { - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_C_INIT diff --git a/build2/c/init.cxx b/build2/c/init.cxx index c5b8ac8..da57223 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -2,16 +2,16 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include +#include +#include +#include -#include -#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/c/init.hxx b/build2/c/init.hxx new file mode 100644 index 0000000..e18a5df --- /dev/null +++ b/build2/c/init.hxx @@ -0,0 +1,37 @@ +// file : build2/c/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_C_INIT_HXX +#define BUILD2_C_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace c + { + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_C_INIT_HXX diff --git a/build2/c/target b/build2/c/target deleted file mode 100644 index 5d7566b..0000000 --- a/build2/c/target +++ /dev/null @@ -1,22 +0,0 @@ -// file : build2/c/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_C_TARGET -#define BUILD2_C_TARGET - -#include -#include - -#include - -namespace build2 -{ - namespace c - { - using cc::h; - using cc::c; - } -} - -#endif // BUILD2_C_TARGET diff --git a/build2/c/target.hxx b/build2/c/target.hxx new file mode 100644 index 0000000..d92405d --- /dev/null +++ b/build2/c/target.hxx @@ -0,0 +1,22 @@ +// file : build2/c/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_C_TARGET_HXX +#define BUILD2_C_TARGET_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace c + { + using cc::h; + using cc::c; + } +} + +#endif // BUILD2_C_TARGET_HXX diff --git a/build2/cc/common b/build2/cc/common deleted file mode 100644 index 5a459b8..0000000 --- a/build2/cc/common +++ /dev/null @@ -1,289 +0,0 @@ -// file : build2/cc/common -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_COMMON -#define BUILD2_CC_COMMON - -#include -#include - -#include - -#include - -#include - -namespace build2 -{ - namespace cc - { - // Data entries that define a concrete c-family module (e.g., c or cxx). - // These classes are used as a virtual bases by the rules as well as the - // modules. This way the member variables can be referenced as is, without - // any extra decorations (in other words, it is a bunch of data members - // that can be shared between several classes/instances). - // - struct config_data - { - lang x_lang; - - const char* x; // Module name ("c", "cxx"). - const char* x_name; // Compiler name ("c", "c++"). - const char* x_default; // Compiler default ("gcc", "g++"). - - const variable& config_x; - const variable& config_x_poptions; - const variable& config_x_coptions; - const variable& config_x_loptions; - const variable& config_x_libs; - - const variable& x_path; // Compiler process path. - const variable& x_sys_lib_dirs; // System library search directories. - const variable& x_sys_inc_dirs; // Extra header search directories. - - const variable& x_poptions; - const variable& x_coptions; - const variable& x_loptions; - const variable& x_libs; - - const variable& c_poptions; // cc.* - const variable& c_coptions; - const variable& c_loptions; - const variable& c_libs; - - const variable& x_export_poptions; - const variable& x_export_coptions; - const variable& x_export_loptions; - const variable& x_export_libs; - - const variable& c_export_poptions; // cc.export.* - const variable& c_export_coptions; - const variable& c_export_loptions; - const variable& c_export_libs; - - const variable& c_type; // cc.type - const variable& c_system; // cc.system - - const variable& x_std; - - const variable& x_id; - const variable& x_id_type; - const variable& x_id_variant; - - const variable& x_version; - const variable& x_version_major; - const variable& x_version_minor; - const variable& x_version_patch; - const variable& x_version_build; - - const variable& x_signature; - const variable& x_checksum; - - const variable& x_target; - const variable& x_target_cpu; - const variable& x_target_vendor; - const variable& x_target_system; - const variable& x_target_version; - const variable& x_target_class; - }; - - struct data: config_data - { - const char* x_compile; // Rule names. - const char* x_link; - const char* x_install; - const char* x_uninstall; - - // Cached values for some commonly-used variables/values. - // - const string& cid; // x.id - const target_triplet& ctg; // x.target - const string& tsys; // x.target.system - const string& tclass; // x.target.class - - const string& tstd; // Translated x_std value (can be empty). - - const process_path* pkgconfig; // pkgconfig.path (can be NULL). - const dir_paths& sys_lib_dirs; // x.sys_lib_dirs - const dir_paths& sys_inc_dirs; // x.sys_inc_dirs - - const target_type& x_src; // Source target type (c{}, cxx{}). - - // Array of target types that are considered headers. Keep them in the - // most likely to appear order and terminate with NULL. - // - const target_type* const* x_hdr; - - template - bool - x_header (const T& t) const - { - for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) - if (t.is_a (**ht)) - return true; - - return false; - } - - // Array of target types that can be #include'd. Used to reverse-lookup - // extensions to target types. Keep them in the most likely to appear - // order and terminate with NULL. - // - const target_type* const* x_inc; - - // Aggregate-like constructor with from-base support. - // - data (const config_data& cd, - const char* compile, - const char* link, - const char* install, - const char* uninstall, - const string& id, - const target_triplet& tg, - const string& std, - const process_path* pkgc, - const dir_paths& sld, - const dir_paths& sid, - const target_type& src, - const target_type* const* hdr, - const target_type* const* inc) - : config_data (cd), - x_compile (compile), - x_link (link), - x_install (install), - x_uninstall (uninstall), - cid (id), ctg (tg), tsys (ctg.system), tclass (ctg.class_), - tstd (std), - pkgconfig (pkgc), sys_lib_dirs (sld), sys_inc_dirs (sid), - x_src (src), x_hdr (hdr), x_inc (inc) {} - }; - - class common: protected data - { - public: - common (data&& d): data (move (d)) {} - - // Language standard (x.std) mapping. - // - void - append_std (cstrings& args) const - { - if (!tstd.empty ()) - args.push_back (tstd.c_str ()); - } - - void - hash_std (sha256& cs) const - { - if (!tstd.empty ()) - cs.append (tstd); - } - - // Library handling. - // - public: - void - process_libraries ( - action, - const scope&, - lorder, - const dir_paths&, - const file&, - bool, - const function&, - const function&, - const function&, - bool = false) const; - - const target* - search_library (action act, - const dir_paths& sysd, - optional& usrd, - const prerequisite& p) const - { - const target* r (p.target.load (memory_order_consume)); - - if (r == nullptr) - { - if ((r = search_library (act, sysd, usrd, p.key ())) != nullptr) - { - const target* e (nullptr); - if (!p.target.compare_exchange_strong ( - e, r, - memory_order_release, - memory_order_consume)) - assert (e == r); - } - } - - return r; - } - - public: - const file& - resolve_library (action, - const scope&, - name, - lorder, - const dir_paths&, - optional&) const; - - template - static ulock - insert_library (T*&, - const string&, - const dir_path&, - optional, - bool, - tracer&); - - target* - search_library (action, - const dir_paths&, - optional&, - const prerequisite_key&, - bool existing = false) const; - - const target* - search_library_existing (action act, - const dir_paths& sysd, - optional& usrd, - const prerequisite_key& pk) const - { - return search_library (act, sysd, usrd, pk, true); - } - - dir_paths - extract_library_dirs (const scope&) const; - - bool - pkgconfig_extract (action, - const scope&, - bin::lib&, - bin::liba*, - bin::libs*, - const optional&, - const string&, - const dir_path&, - const dir_paths&) const; // pkgconfig.cxx - - // Alternative search logic for VC (msvc.cxx). - // - bin::liba* - msvc_search_static (const process_path&, - const dir_path&, - const prerequisite_key&, - bool existing) const; - - bin::libs* - msvc_search_shared (const process_path&, - const dir_path&, - const prerequisite_key&, - bool existing) const; - - }; - } -} - -#endif // BUILD2_CC_COMMON diff --git a/build2/cc/common.cxx b/build2/cc/common.cxx index 88eb45d..59feb9b 100644 --- a/build2/cc/common.cxx +++ b/build2/cc/common.cxx @@ -2,17 +2,17 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // import() -#include -#include -#include -#include -#include -#include +#include // import() +#include +#include +#include +#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx new file mode 100644 index 0000000..0c8ff2b --- /dev/null +++ b/build2/cc/common.hxx @@ -0,0 +1,289 @@ +// file : build2/cc/common.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_COMMON_HXX +#define BUILD2_CC_COMMON_HXX + +#include +#include + +#include + +#include + +#include + +namespace build2 +{ + namespace cc + { + // Data entries that define a concrete c-family module (e.g., c or cxx). + // These classes are used as a virtual bases by the rules as well as the + // modules. This way the member variables can be referenced as is, without + // any extra decorations (in other words, it is a bunch of data members + // that can be shared between several classes/instances). + // + struct config_data + { + lang x_lang; + + const char* x; // Module name ("c", "cxx"). + const char* x_name; // Compiler name ("c", "c++"). + const char* x_default; // Compiler default ("gcc", "g++"). + + const variable& config_x; + const variable& config_x_poptions; + const variable& config_x_coptions; + const variable& config_x_loptions; + const variable& config_x_libs; + + const variable& x_path; // Compiler process path. + const variable& x_sys_lib_dirs; // System library search directories. + const variable& x_sys_inc_dirs; // Extra header search directories. + + const variable& x_poptions; + const variable& x_coptions; + const variable& x_loptions; + const variable& x_libs; + + const variable& c_poptions; // cc.* + const variable& c_coptions; + const variable& c_loptions; + const variable& c_libs; + + const variable& x_export_poptions; + const variable& x_export_coptions; + const variable& x_export_loptions; + const variable& x_export_libs; + + const variable& c_export_poptions; // cc.export.* + const variable& c_export_coptions; + const variable& c_export_loptions; + const variable& c_export_libs; + + const variable& c_type; // cc.type + const variable& c_system; // cc.system + + const variable& x_std; + + const variable& x_id; + const variable& x_id_type; + const variable& x_id_variant; + + const variable& x_version; + const variable& x_version_major; + const variable& x_version_minor; + const variable& x_version_patch; + const variable& x_version_build; + + const variable& x_signature; + const variable& x_checksum; + + const variable& x_target; + const variable& x_target_cpu; + const variable& x_target_vendor; + const variable& x_target_system; + const variable& x_target_version; + const variable& x_target_class; + }; + + struct data: config_data + { + const char* x_compile; // Rule names. + const char* x_link; + const char* x_install; + const char* x_uninstall; + + // Cached values for some commonly-used variables/values. + // + const string& cid; // x.id + const target_triplet& ctg; // x.target + const string& tsys; // x.target.system + const string& tclass; // x.target.class + + const string& tstd; // Translated x_std value (can be empty). + + const process_path* pkgconfig; // pkgconfig.path (can be NULL). + const dir_paths& sys_lib_dirs; // x.sys_lib_dirs + const dir_paths& sys_inc_dirs; // x.sys_inc_dirs + + const target_type& x_src; // Source target type (c{}, cxx{}). + + // Array of target types that are considered headers. Keep them in the + // most likely to appear order and terminate with NULL. + // + const target_type* const* x_hdr; + + template + bool + x_header (const T& t) const + { + for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) + if (t.is_a (**ht)) + return true; + + return false; + } + + // Array of target types that can be #include'd. Used to reverse-lookup + // extensions to target types. Keep them in the most likely to appear + // order and terminate with NULL. + // + const target_type* const* x_inc; + + // Aggregate-like constructor with from-base support. + // + data (const config_data& cd, + const char* compile, + const char* link, + const char* install, + const char* uninstall, + const string& id, + const target_triplet& tg, + const string& std, + const process_path* pkgc, + const dir_paths& sld, + const dir_paths& sid, + const target_type& src, + const target_type* const* hdr, + const target_type* const* inc) + : config_data (cd), + x_compile (compile), + x_link (link), + x_install (install), + x_uninstall (uninstall), + cid (id), ctg (tg), tsys (ctg.system), tclass (ctg.class_), + tstd (std), + pkgconfig (pkgc), sys_lib_dirs (sld), sys_inc_dirs (sid), + x_src (src), x_hdr (hdr), x_inc (inc) {} + }; + + class common: protected data + { + public: + common (data&& d): data (move (d)) {} + + // Language standard (x.std) mapping. + // + void + append_std (cstrings& args) const + { + if (!tstd.empty ()) + args.push_back (tstd.c_str ()); + } + + void + hash_std (sha256& cs) const + { + if (!tstd.empty ()) + cs.append (tstd); + } + + // Library handling. + // + public: + void + process_libraries ( + action, + const scope&, + lorder, + const dir_paths&, + const file&, + bool, + const function&, + const function&, + const function&, + bool = false) const; + + const target* + search_library (action act, + const dir_paths& sysd, + optional& usrd, + const prerequisite& p) const + { + const target* r (p.target.load (memory_order_consume)); + + if (r == nullptr) + { + if ((r = search_library (act, sysd, usrd, p.key ())) != nullptr) + { + const target* e (nullptr); + if (!p.target.compare_exchange_strong ( + e, r, + memory_order_release, + memory_order_consume)) + assert (e == r); + } + } + + return r; + } + + public: + const file& + resolve_library (action, + const scope&, + name, + lorder, + const dir_paths&, + optional&) const; + + template + static ulock + insert_library (T*&, + const string&, + const dir_path&, + optional, + bool, + tracer&); + + target* + search_library (action, + const dir_paths&, + optional&, + const prerequisite_key&, + bool existing = false) const; + + const target* + search_library_existing (action act, + const dir_paths& sysd, + optional& usrd, + const prerequisite_key& pk) const + { + return search_library (act, sysd, usrd, pk, true); + } + + dir_paths + extract_library_dirs (const scope&) const; + + bool + pkgconfig_extract (action, + const scope&, + bin::lib&, + bin::liba*, + bin::libs*, + const optional&, + const string&, + const dir_path&, + const dir_paths&) const; // pkgconfig.cxx + + // Alternative search logic for VC (msvc.cxx). + // + bin::liba* + msvc_search_static (const process_path&, + const dir_path&, + const prerequisite_key&, + bool existing) const; + + bin::libs* + msvc_search_shared (const process_path&, + const dir_path&, + const prerequisite_key&, + bool existing) const; + + }; + } +} + +#endif // BUILD2_CC_COMMON_HXX diff --git a/build2/cc/compile b/build2/cc/compile deleted file mode 100644 index 2986b7d..0000000 --- a/build2/cc/compile +++ /dev/null @@ -1,94 +0,0 @@ -// file : build2/cc/compile -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_COMPILE -#define BUILD2_CC_COMPILE - -#include - -#include -#include - -#include - -#include -#include - -namespace build2 -{ - class depdb; - - namespace cc - { - class compile: public rule, virtual common - { - public: - compile (data&&); - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - target_state - perform_update (action, const target&) const; - - target_state - perform_clean (action, const target&) const; - - private: - void - append_lib_options (const scope&, - cstrings&, - const target&, - action, lorder) const; - - void - hash_lib_options (const scope&, - sha256&, - const target&, - action, lorder) const; - - // Mapping of include prefixes (e.g., foo in ) for auto- - // generated headers to directories where they will be generated. - // - // We are using a prefix map of directories (dir_path_map) instead of - // just a map in order to also cover sub-paths (e.g., if - // we continue with the example). Specifically, we need to make sure we - // don't treat foobar as a sub-directory of foo. - // - // @@ The keys should be normalized. - // - using prefix_map = butl::dir_path_map; - - void - append_prefixes (prefix_map&, const target&, const variable&) const; - - void - append_lib_prefixes (const scope&, - prefix_map&, - target&, - action, lorder) const; - - prefix_map - build_prefix_map (const scope&, target&, action, lorder) const; - - // Reverse-lookup target type from extension. - // - const target_type* - map_extension (const scope&, const string&, const string&) const; - - // Header dependency injection. - // - void - inject (action, target&, lorder, const file&, depdb&) const; - - private: - const string rule_id; - }; - } -} - -#endif // BUILD2_CC_COMPILE diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index 7e37c44..b8b362d 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -2,22 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // exit() #include // cerr -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include -#include // h -#include +#include // h +#include using namespace std; using namespace butl; diff --git a/build2/cc/compile.hxx b/build2/cc/compile.hxx new file mode 100644 index 0000000..bee13f2 --- /dev/null +++ b/build2/cc/compile.hxx @@ -0,0 +1,94 @@ +// file : build2/cc/compile.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_COMPILE_HXX +#define BUILD2_CC_COMPILE_HXX + +#include + +#include +#include + +#include + +#include +#include + +namespace build2 +{ + class depdb; + + namespace cc + { + class compile: public rule, virtual common + { + public: + compile (data&&); + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + target_state + perform_update (action, const target&) const; + + target_state + perform_clean (action, const target&) const; + + private: + void + append_lib_options (const scope&, + cstrings&, + const target&, + action, lorder) const; + + void + hash_lib_options (const scope&, + sha256&, + const target&, + action, lorder) const; + + // Mapping of include prefixes (e.g., foo in ) for auto- + // generated headers to directories where they will be generated. + // + // We are using a prefix map of directories (dir_path_map) instead of + // just a map in order to also cover sub-paths (e.g., if + // we continue with the example). Specifically, we need to make sure we + // don't treat foobar as a sub-directory of foo. + // + // @@ The keys should be normalized. + // + using prefix_map = butl::dir_path_map; + + void + append_prefixes (prefix_map&, const target&, const variable&) const; + + void + append_lib_prefixes (const scope&, + prefix_map&, + target&, + action, lorder) const; + + prefix_map + build_prefix_map (const scope&, target&, action, lorder) const; + + // Reverse-lookup target type from extension. + // + const target_type* + map_extension (const scope&, const string&, const string&) const; + + // Header dependency injection. + // + void + inject (action, target&, lorder, const file&, depdb&) const; + + private: + const string rule_id; + }; + } +} + +#endif // BUILD2_CC_COMPILE_HXX diff --git a/build2/cc/gcc.cxx b/build2/cc/gcc.cxx index c16191a..b5dd236 100644 --- a/build2/cc/gcc.cxx +++ b/build2/cc/gcc.cxx @@ -2,18 +2,18 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include -#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/guess b/build2/cc/guess deleted file mode 100644 index 593bd85..0000000 --- a/build2/cc/guess +++ /dev/null @@ -1,141 +0,0 @@ -// file : build2/cc/guess -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_GUESS -#define BUILD2_CC_GUESS - -#include -#include - -#include - -namespace build2 -{ - namespace cc - { - // Compiler id consisting of a type and optional variant. If the variant - // is not empty, then the id is spelled out as 'type-variant', similar to - // target triplets (this also means that the type cannot contain '-'). - // - // Currently recognized compilers and their ids: - // - // gcc GCC gcc/g++ - // clang Vanilla Clang clang/clang++ - // clang-apple Apple Clang clang/clang++ and the gcc/g++ "alias" - // icc Intel icc/icpc - // msvc Microsoft cl.exe - // - struct compiler_id - { - std::string type; - std::string variant; - - bool - empty () const {return type.empty ();} - - std::string - string () const {return variant.empty () ? type : type + "-" + variant;} - }; - - inline ostream& - operator<< (ostream& os, const compiler_id& id) - { - return os << id.string (); - } - - // Compiler version. Here we map the various compiler version formats to - // something that resembles the MAJOR.MINOR.PATCH-BUILD form of the - // Semantic Versioning. While the MAJOR.MINOR part is relatively - // straightforward, PATCH may be empty and BUILD can contain pretty much - // anything (including spaces). - // - // gcc A.B.C[ ...] {A, B, C, ...} - // clang A.B.C[( |-)...] {A, B, C, ...} - // clang-apple A.B[.C] ... {A, B, C, ...} - // icc A.B[.C.D] ... {A, B, C, D ...} - // msvc A.B.C[.D] {A, B, C, D} - // - // Note that the clang-apple version is a custom Apple version and does - // not correspond to the vanilla clang version. - // - struct compiler_version - { - std::string string; - - // Currently all the compilers that we support have numeric MAJOR, - // MINOR, and PATCH components and it makes sense to represent them as - // integers for easy comparison. If we meet a compiler for which this - // doesn't hold, then we will probably just set these to 0 and let the - // user deal with the string representation. - // - uint64_t major; - uint64_t minor; - uint64_t patch; - std::string build; - }; - - // Compiler information. - // - // The signature is normally the -v/--version line that was used to guess - // the compiler id and its version. - // - // The checksum is used to detect compiler changes. It is calculated in a - // compiler-specific manner (usually the output of -v/--version) and is - // not bulletproof (e.g., it most likely won't detect that the underlying - // assembler or linker has changed). However, it should detect most - // common cases, such as an upgrade to a new version or a configuration - // change. - // - // Note that we assume the checksum incorporates the (default) target so - // that if the compiler changes but only in what it targets, then the - // checksum will still change. This is currently the case for all the - // compilers that we support. - // - // The target is the compiler's traget architecture triplet. Note that - // unlike all the preceding fields, this one takes into account the - // compile options (e.g., -m32). - // - // The cc_pattern is the toolchain program pattern that could sometimes be - // derived for some toolchains. For example, i686-w64-mingw32-*-4.9. - // - // The bin_pattern is the binutils program pattern that could sometimes be - // derived for some toolchains. For example, i686-w64-mingw32-*. If the - // pattern could not be derived, then it could contain a fallback search - // directory, in which case it will end with a directory separator but - // will not contain '*'. - // - struct compiler_info - { - process_path path; - compiler_id id; - compiler_version version; - string signature; - string checksum; - string target; - string cc_pattern; - string bin_pattern; - }; - - // In a sense this is analagous to the language standard which we handle - // via a virtual function in common. However, duplicating this hairy ball - // of fur in multiple places doesn't seem wise, especially considering - // that most of it will be the same, at least for C and C++. - // - compiler_info - guess (lang, - const path& xc, - const strings* c_coptions, - const strings* x_coptions); - - // Given a language, toolchain id, and optionally a pattern, return an - // appropriate default compiler path. - // - // For example, for (lang::cxx, gcc, *-4.9) we will get g++-4.9. - // - path - guess_default (lang, const string& cid, const string* pattern); - } -} - -#endif // BUILD2_CC_GUESS diff --git a/build2/cc/guess.cxx b/build2/cc/guess.cxx index 8b45420..0569bdc 100644 --- a/build2/cc/guess.cxx +++ b/build2/cc/guess.cxx @@ -2,11 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strlen(), strchr() -#include +#include using namespace std; @@ -946,8 +946,9 @@ namespace build2 // Now we need to map x86, x64, and ARM to the target triplets. The // problem is, there aren't any established ones so we got to invent - // them ourselves. Based on the discussion in , - // we need something in the CPU-VENDOR-OS-ABI form. + // them ourselves. Based on the discussion in + // , we need something in the + // CPU-VENDOR-OS-ABI form. // // The CPU part is fairly straightforward with x86 mapped to 'i386' (or // maybe 'i686'), x64 to 'x86_64', and ARM to 'arm' (it could also diff --git a/build2/cc/guess.hxx b/build2/cc/guess.hxx new file mode 100644 index 0000000..dbd06e3 --- /dev/null +++ b/build2/cc/guess.hxx @@ -0,0 +1,141 @@ +// file : build2/cc/guess.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_GUESS_HXX +#define BUILD2_CC_GUESS_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cc + { + // Compiler id consisting of a type and optional variant. If the variant + // is not empty, then the id is spelled out as 'type-variant', similar to + // target triplets (this also means that the type cannot contain '-'). + // + // Currently recognized compilers and their ids: + // + // gcc GCC gcc/g++ + // clang Vanilla Clang clang/clang++ + // clang-apple Apple Clang clang/clang++ and the gcc/g++ "alias" + // icc Intel icc/icpc + // msvc Microsoft cl.exe + // + struct compiler_id + { + std::string type; + std::string variant; + + bool + empty () const {return type.empty ();} + + std::string + string () const {return variant.empty () ? type : type + "-" + variant;} + }; + + inline ostream& + operator<< (ostream& os, const compiler_id& id) + { + return os << id.string (); + } + + // Compiler version. Here we map the various compiler version formats to + // something that resembles the MAJOR.MINOR.PATCH-BUILD form of the + // Semantic Versioning. While the MAJOR.MINOR part is relatively + // straightforward, PATCH may be empty and BUILD can contain pretty much + // anything (including spaces). + // + // gcc A.B.C[ ...] {A, B, C, ...} + // clang A.B.C[( |-)...] {A, B, C, ...} + // clang-apple A.B[.C] ... {A, B, C, ...} + // icc A.B[.C.D] ... {A, B, C, D ...} + // msvc A.B.C[.D] {A, B, C, D} + // + // Note that the clang-apple version is a custom Apple version and does + // not correspond to the vanilla clang version. + // + struct compiler_version + { + std::string string; + + // Currently all the compilers that we support have numeric MAJOR, + // MINOR, and PATCH components and it makes sense to represent them as + // integers for easy comparison. If we meet a compiler for which this + // doesn't hold, then we will probably just set these to 0 and let the + // user deal with the string representation. + // + uint64_t major; + uint64_t minor; + uint64_t patch; + std::string build; + }; + + // Compiler information. + // + // The signature is normally the -v/--version line that was used to guess + // the compiler id and its version. + // + // The checksum is used to detect compiler changes. It is calculated in a + // compiler-specific manner (usually the output of -v/--version) and is + // not bulletproof (e.g., it most likely won't detect that the underlying + // assembler or linker has changed). However, it should detect most + // common cases, such as an upgrade to a new version or a configuration + // change. + // + // Note that we assume the checksum incorporates the (default) target so + // that if the compiler changes but only in what it targets, then the + // checksum will still change. This is currently the case for all the + // compilers that we support. + // + // The target is the compiler's traget architecture triplet. Note that + // unlike all the preceding fields, this one takes into account the + // compile options (e.g., -m32). + // + // The cc_pattern is the toolchain program pattern that could sometimes be + // derived for some toolchains. For example, i686-w64-mingw32-*-4.9. + // + // The bin_pattern is the binutils program pattern that could sometimes be + // derived for some toolchains. For example, i686-w64-mingw32-*. If the + // pattern could not be derived, then it could contain a fallback search + // directory, in which case it will end with a directory separator but + // will not contain '*'. + // + struct compiler_info + { + process_path path; + compiler_id id; + compiler_version version; + string signature; + string checksum; + string target; + string cc_pattern; + string bin_pattern; + }; + + // In a sense this is analagous to the language standard which we handle + // via a virtual function in common. However, duplicating this hairy ball + // of fur in multiple places doesn't seem wise, especially considering + // that most of it will be the same, at least for C and C++. + // + compiler_info + guess (lang, + const path& xc, + const strings* c_coptions, + const strings* x_coptions); + + // Given a language, toolchain id, and optionally a pattern, return an + // appropriate default compiler path. + // + // For example, for (lang::cxx, gcc, *-4.9) we will get g++-4.9. + // + path + guess_default (lang, const string& cid, const string* pattern); + } +} + +#endif // BUILD2_CC_GUESS_HXX diff --git a/build2/cc/init b/build2/cc/init deleted file mode 100644 index c969505..0000000 --- a/build2/cc/init +++ /dev/null @@ -1,64 +0,0 @@ -// file : build2/cc/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_INIT -#define BUILD2_CC_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace cc - { - bool - core_vars_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - core_config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - core_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_CC_INIT diff --git a/build2/cc/init.cxx b/build2/cc/init.cxx index bc598c3..8d20573 100644 --- a/build2/cc/init.cxx +++ b/build2/cc/init.cxx @@ -2,15 +2,15 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include +#include +#include +#include -#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/init.hxx b/build2/cc/init.hxx new file mode 100644 index 0000000..9eab424 --- /dev/null +++ b/build2/cc/init.hxx @@ -0,0 +1,64 @@ +// file : build2/cc/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_INIT_HXX +#define BUILD2_CC_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cc + { + bool + core_vars_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + core_config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + core_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_CC_INIT_HXX diff --git a/build2/cc/install b/build2/cc/install deleted file mode 100644 index e229e94..0000000 --- a/build2/cc/install +++ /dev/null @@ -1,48 +0,0 @@ -// file : build2/cc/install -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_INSTALL -#define BUILD2_CC_INSTALL - -#include -#include - -#include - -#include -#include - -namespace build2 -{ - namespace cc - { - class link; - - class install: public build2::install::file_rule, virtual common - { - public: - install (data&&, const link&); - - virtual const target* - filter (action, const target&, prerequisite_member) const override; - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - virtual void - install_extra (const file&, const install_dir&) const override; - - virtual bool - uninstall_extra (const file&, const install_dir&) const override; - - private: - const link& link_; - }; - } -} - -#endif // BUILD2_CC_INSTALL diff --git a/build2/cc/install.cxx b/build2/cc/install.cxx index e1e5719..e09a62f 100644 --- a/build2/cc/install.cxx +++ b/build2/cc/install.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include +#include -#include +#include -#include // match() -#include +#include // match() +#include using namespace std; diff --git a/build2/cc/install.hxx b/build2/cc/install.hxx new file mode 100644 index 0000000..b7d92fb --- /dev/null +++ b/build2/cc/install.hxx @@ -0,0 +1,48 @@ +// file : build2/cc/install.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_INSTALL_HXX +#define BUILD2_CC_INSTALL_HXX + +#include +#include + +#include + +#include +#include + +namespace build2 +{ + namespace cc + { + class link; + + class install: public build2::install::file_rule, virtual common + { + public: + install (data&&, const link&); + + virtual const target* + filter (action, const target&, prerequisite_member) const override; + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + virtual void + install_extra (const file&, const install_dir&) const override; + + virtual bool + uninstall_extra (const file&, const install_dir&) const override; + + private: + const link& link_; + }; + } +} + +#endif // BUILD2_CC_INSTALL_HXX diff --git a/build2/cc/link b/build2/cc/link deleted file mode 100644 index 0dace7c..0000000 --- a/build2/cc/link +++ /dev/null @@ -1,130 +0,0 @@ -// file : build2/cc/link -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_LINK -#define BUILD2_CC_LINK - -#include - -#include -#include - -#include - -#include -#include - -namespace build2 -{ - namespace cc - { - class link: public rule, virtual common - { - public: - link (data&&); - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - target_state - perform_update (action, const target&) const; - - target_state - perform_clean (action, const target&) const; - - private: - friend class install; - - // Shared library paths. - // - struct libs_paths - { - // If any (except real) is empty, then it is the same as the next - // one. Except for intermediate, for which empty indicates that it is - // not used. - // - // The libs{} path is always the real path. On Windows the link path - // is the import library. - // - const path link; // What we link: libfoo.so - const path soname; // SONAME: libfoo-1.so, libfoo.so.1 - const path interm; // Intermediate: libfoo.so.1.2 - const path& real; // Real: libfoo.so.1.2.3 - - inline const path& - effect_link () const {return link.empty () ? effect_soname () : link;} - - inline const path& - effect_soname () const {return soname.empty () ? real : soname;} - - // Cleanup pattern used to remove previous versions. If empty, no - // cleanup is performed. The above (current) names are automatically - // filtered out. - // - const path clean; - }; - - libs_paths - derive_libs_paths (file&) const; - - // Library handling. - // - void - append_libraries (strings&, - const file&, bool, - const scope&, action, lorder) const; - - void - hash_libraries (sha256&, - const file&, bool, - const scope&, action, lorder) const; - - void - rpath_libraries (strings&, - const target&, - const scope&, action, lorder, - bool) const; - - // Windows rpath emulation (windows-rpath.cxx). - // - struct windows_dll - { - const string& dll; - const string* pdb; // NULL if none. - string pdb_storage; - - bool operator< (const windows_dll& y) const {return dll < y.dll;} - }; - - using windows_dlls = std::set; - - timestamp - windows_rpath_timestamp (const file&, - const scope&, - action, lorder) const; - - windows_dlls - windows_rpath_dlls (const file&, const scope&, action, lorder) const; - - void - windows_rpath_assembly (const file&, const scope&, action, lorder, - const string&, - timestamp, - bool) const; - - // Windows-specific (windows-manifest.cxx). - // - pair - windows_manifest (const file&, bool rpath_assembly) const; - - private: - const string rule_id; - }; - } -} - -#endif // BUILD2_CC_LINK diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 80281f7..1b2f306 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -2,27 +2,27 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include #include // exit() #include // cerr -#include -#include // file_exists() +#include +#include // file_exists() -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include -#include // c -#include +#include // c +#include using namespace std; using namespace butl; diff --git a/build2/cc/link.hxx b/build2/cc/link.hxx new file mode 100644 index 0000000..4dc722a --- /dev/null +++ b/build2/cc/link.hxx @@ -0,0 +1,130 @@ +// file : build2/cc/link.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_LINK_HXX +#define BUILD2_CC_LINK_HXX + +#include + +#include +#include + +#include + +#include +#include + +namespace build2 +{ + namespace cc + { + class link: public rule, virtual common + { + public: + link (data&&); + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + target_state + perform_update (action, const target&) const; + + target_state + perform_clean (action, const target&) const; + + private: + friend class install; + + // Shared library paths. + // + struct libs_paths + { + // If any (except real) is empty, then it is the same as the next + // one. Except for intermediate, for which empty indicates that it is + // not used. + // + // The libs{} path is always the real path. On Windows the link path + // is the import library. + // + const path link; // What we link: libfoo.so + const path soname; // SONAME: libfoo-1.so, libfoo.so.1 + const path interm; // Intermediate: libfoo.so.1.2 + const path& real; // Real: libfoo.so.1.2.3 + + inline const path& + effect_link () const {return link.empty () ? effect_soname () : link;} + + inline const path& + effect_soname () const {return soname.empty () ? real : soname;} + + // Cleanup pattern used to remove previous versions. If empty, no + // cleanup is performed. The above (current) names are automatically + // filtered out. + // + const path clean; + }; + + libs_paths + derive_libs_paths (file&) const; + + // Library handling. + // + void + append_libraries (strings&, + const file&, bool, + const scope&, action, lorder) const; + + void + hash_libraries (sha256&, + const file&, bool, + const scope&, action, lorder) const; + + void + rpath_libraries (strings&, + const target&, + const scope&, action, lorder, + bool) const; + + // Windows rpath emulation (windows-rpath.cxx). + // + struct windows_dll + { + const string& dll; + const string* pdb; // NULL if none. + string pdb_storage; + + bool operator< (const windows_dll& y) const {return dll < y.dll;} + }; + + using windows_dlls = std::set; + + timestamp + windows_rpath_timestamp (const file&, + const scope&, + action, lorder) const; + + windows_dlls + windows_rpath_dlls (const file&, const scope&, action, lorder) const; + + void + windows_rpath_assembly (const file&, const scope&, action, lorder, + const string&, + timestamp, + bool) const; + + // Windows-specific (windows-manifest.cxx). + // + pair + windows_manifest (const file&, bool rpath_assembly) const; + + private: + const string rule_id; + }; + } +} + +#endif // BUILD2_CC_LINK_HXX diff --git a/build2/cc/module b/build2/cc/module deleted file mode 100644 index 3c5ec61..0000000 --- a/build2/cc/module +++ /dev/null @@ -1,68 +0,0 @@ -// file : build2/cc/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_MODULE -#define BUILD2_CC_MODULE - -#include -#include - -#include -#include - -#include - -#include -#include -#include - -namespace build2 -{ - namespace cc - { - struct compiler_info; - - class config_module: public module_base, public virtual config_data - { - public: - explicit - config_module (config_data&& d) : config_data (move (d)) {} - - void - init (scope&, const location&, const variable_map&); - - // Translate the x.std value to the standard-selecting option if there - // is any. - // - virtual string - translate_std (const compiler_info&, scope&, const string&) const = 0; - - string tstd; - - private: - dir_paths - gcc_library_search_paths (process_path&, scope&) const; // gcc.cxx - - dir_paths - msvc_library_search_paths (process_path&, scope&) const; // msvc.cxx - }; - - class module: public module_base, protected virtual common, - link, compile, install - { - public: - explicit - module (data&& d) - : common (move (d)), - link (move (d)), - compile (move (d)), - install (move (d), *this) {} - - void - init (scope&, const location&, const variable_map&); - }; - } -} - -#endif // BUILD2_CC_MODULE diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index 7df71ba..6d8c799 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -2,20 +2,20 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // left, setw() -#include -#include -#include +#include +#include +#include -#include +#include -#include -#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/module.hxx b/build2/cc/module.hxx new file mode 100644 index 0000000..643cf89 --- /dev/null +++ b/build2/cc/module.hxx @@ -0,0 +1,68 @@ +// file : build2/cc/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_MODULE_HXX +#define BUILD2_CC_MODULE_HXX + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace build2 +{ + namespace cc + { + struct compiler_info; + + class config_module: public module_base, public virtual config_data + { + public: + explicit + config_module (config_data&& d) : config_data (move (d)) {} + + void + init (scope&, const location&, const variable_map&); + + // Translate the x.std value to the standard-selecting option if there + // is any. + // + virtual string + translate_std (const compiler_info&, scope&, const string&) const = 0; + + string tstd; + + private: + dir_paths + gcc_library_search_paths (process_path&, scope&) const; // gcc.cxx + + dir_paths + msvc_library_search_paths (process_path&, scope&) const; // msvc.cxx + }; + + class module: public module_base, protected virtual common, + link, compile, install + { + public: + explicit + module (data&& d) + : common (move (d)), + link (move (d)), + compile (move (d)), + install (move (d), *this) {} + + void + init (scope&, const location&, const variable_map&); + }; + } +} + +#endif // BUILD2_CC_MODULE_HXX diff --git a/build2/cc/msvc.cxx b/build2/cc/msvc.cxx index aa9389f..e3765cd 100644 --- a/build2/cc/msvc.cxx +++ b/build2/cc/msvc.cxx @@ -4,19 +4,19 @@ #include // cerr -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include -#include +#include -#include -#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 00ab541..1b67f5f 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -2,19 +2,19 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include -#include -#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cc/target b/build2/cc/target deleted file mode 100644 index 6992609..0000000 --- a/build2/cc/target +++ /dev/null @@ -1,63 +0,0 @@ -// file : build2/cc/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_TARGET -#define BUILD2_CC_TARGET - -#include -#include - -#include - -namespace build2 -{ - namespace cc - { - // This is an abstract base target for all c-common source files. We use - // this arrangement in rule matching to detect "unknown" (to this rule) - // source files that it cannot handle but should not ignore either. For - // example, a C link rule that sees a C++ source file. - // - class cc: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const = 0; - }; - - // There is hardly a c-family compilation without a C header inclusion. - // As a result, this target type is registered for any c-family module. - // - class h: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // This one we define in cc but the target type is only registered by the - // c module. This way we can implement rule chaining without jumping - // through too many hoops (like resolving target type dynamically) but - // also without relaxing things too much (i.e., the user still won't be - // able to refer to c{} without loading the c module). - // - class c: public cc - { - public: - using cc::cc; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - } -} - -#endif // BUILD2_CC_TARGET diff --git a/build2/cc/target.cxx b/build2/cc/target.cxx index a734953..5fbf67d 100644 --- a/build2/cc/target.cxx +++ b/build2/cc/target.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/cc/target.hxx b/build2/cc/target.hxx new file mode 100644 index 0000000..4c9e4cb --- /dev/null +++ b/build2/cc/target.hxx @@ -0,0 +1,63 @@ +// file : build2/cc/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_TARGET_HXX +#define BUILD2_CC_TARGET_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cc + { + // This is an abstract base target for all c-common source files. We use + // this arrangement in rule matching to detect "unknown" (to this rule) + // source files that it cannot handle but should not ignore either. For + // example, a C link rule that sees a C++ source file. + // + class cc: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const = 0; + }; + + // There is hardly a c-family compilation without a C header inclusion. + // As a result, this target type is registered for any c-family module. + // + class h: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // This one we define in cc but the target type is only registered by the + // c module. This way we can implement rule chaining without jumping + // through too many hoops (like resolving target type dynamically) but + // also without relaxing things too much (i.e., the user still won't be + // able to refer to c{} without loading the c module). + // + class c: public cc + { + public: + using cc::cc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_CC_TARGET_HXX diff --git a/build2/cc/types b/build2/cc/types deleted file mode 100644 index 68d9949..0000000 --- a/build2/cc/types +++ /dev/null @@ -1,35 +0,0 @@ -// file : build2/cc/types -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_TYPES -#define BUILD2_CC_TYPES - -#include -#include - -namespace build2 -{ - namespace cc - { - // Compiler language. - // - enum class lang {c, cxx}; - - inline ostream& - operator<< (ostream& os, lang l) - { - return os << (l == lang::c ? "C" : "C++"); - } - - // Compile/link output type (executable, static, or shared). - // - enum class otype {e, a, s}; - - // Library link order. - // - enum class lorder {a, s, a_s, s_a}; - } -} - -#endif // BUILD2_CC_TYPES diff --git a/build2/cc/types.hxx b/build2/cc/types.hxx new file mode 100644 index 0000000..59ea67f --- /dev/null +++ b/build2/cc/types.hxx @@ -0,0 +1,35 @@ +// file : build2/cc/types.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_TYPES_HXX +#define BUILD2_CC_TYPES_HXX + +#include +#include + +namespace build2 +{ + namespace cc + { + // Compiler language. + // + enum class lang {c, cxx}; + + inline ostream& + operator<< (ostream& os, lang l) + { + return os << (l == lang::c ? "C" : "C++"); + } + + // Compile/link output type (executable, static, or shared). + // + enum class otype {e, a, s}; + + // Library link order. + // + enum class lorder {a, s, a_s, s_a}; + } +} + +#endif // BUILD2_CC_TYPES_HXX diff --git a/build2/cc/utility b/build2/cc/utility deleted file mode 100644 index ee3cb81..0000000 --- a/build2/cc/utility +++ /dev/null @@ -1,51 +0,0 @@ -// file : build2/cc/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CC_UTILITY -#define BUILD2_CC_UTILITY - -#include -#include - -#include -#include - -#include - -namespace build2 -{ - struct variable; - - namespace cc - { - // Compile/link output type. - // - otype - compile_type (const target&); - - otype - link_type (const target&); - - // Library link order. - // - // The reason we pass scope and not the target is because this function is - // called not only for exe/lib but also for obj as part of the library - // meta-information protocol implementation. Normally the bin.*.lib values - // will be project-wide. With this scheme they can be customized on the - // per-directory basis but not per-target which means all exe/lib in the - // same directory have to have the same link order. - // - lorder - link_order (const scope& base, otype); - - // Given the link order return the library member (liba or libs) to link. - // - const target& - link_member (const bin::lib&, action, lorder); - } -} - -#include - -#endif // BUILD2_CC_UTILITY diff --git a/build2/cc/utility.cxx b/build2/cc/utility.cxx index 4a931af..7a03b54 100644 --- a/build2/cc/utility.cxx +++ b/build2/cc/utility.cxx @@ -2,12 +2,12 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include // search() +#include +#include // search() -#include +#include using namespace std; diff --git a/build2/cc/utility.hxx b/build2/cc/utility.hxx new file mode 100644 index 0000000..e0529af --- /dev/null +++ b/build2/cc/utility.hxx @@ -0,0 +1,51 @@ +// file : build2/cc/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CC_UTILITY_HXX +#define BUILD2_CC_UTILITY_HXX + +#include +#include + +#include +#include + +#include + +namespace build2 +{ + struct variable; + + namespace cc + { + // Compile/link output type. + // + otype + compile_type (const target&); + + otype + link_type (const target&); + + // Library link order. + // + // The reason we pass scope and not the target is because this function is + // called not only for exe/lib but also for obj as part of the library + // meta-information protocol implementation. Normally the bin.*.lib values + // will be project-wide. With this scheme they can be customized on the + // per-directory basis but not per-target which means all exe/lib in the + // same directory have to have the same link order. + // + lorder + link_order (const scope& base, otype); + + // Given the link order return the library member (liba or libs) to link. + // + const target& + link_member (const bin::lib&, action, lorder); + } +} + +#include + +#endif // BUILD2_CC_UTILITY_HXX diff --git a/build2/cc/windows-manifest.cxx b/build2/cc/windows-manifest.cxx index 3a62fcc..d840f67 100644 --- a/build2/cc/windows-manifest.cxx +++ b/build2/cc/windows-manifest.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include + +#include using namespace std; using namespace butl; diff --git a/build2/cc/windows-rpath.cxx b/build2/cc/windows-rpath.cxx index 0b34963..0078944 100644 --- a/build2/cc/windows-rpath.cxx +++ b/build2/cc/windows-rpath.cxx @@ -4,15 +4,15 @@ #include // E* -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cli/init b/build2/cli/init deleted file mode 100644 index 149a436..0000000 --- a/build2/cli/init +++ /dev/null @@ -1,37 +0,0 @@ -// file : build2/cli/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_INIT -#define BUILD2_CLI_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace cli - { - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_CLI_INIT diff --git a/build2/cli/init.cxx b/build2/cli/init.cxx index 3e4ba03..d257efa 100644 --- a/build2/cli/init.cxx +++ b/build2/cli/init.cxx @@ -2,19 +2,19 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include -#include +#include -#include -#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/cli/init.hxx b/build2/cli/init.hxx new file mode 100644 index 0000000..3edb24e --- /dev/null +++ b/build2/cli/init.hxx @@ -0,0 +1,37 @@ +// file : build2/cli/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_INIT_HXX +#define BUILD2_CLI_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cli + { + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_CLI_INIT_HXX diff --git a/build2/cli/rule b/build2/cli/rule deleted file mode 100644 index d783c20..0000000 --- a/build2/cli/rule +++ /dev/null @@ -1,39 +0,0 @@ -// file : build2/cli/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_RULE -#define BUILD2_CLI_RULE - -#include -#include - -#include - -namespace build2 -{ - namespace cli - { - // @@ Redo as two separate rules? - // - class compile: public rule - { - public: - compile () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static target_state - perform_update (action, const target&); - - static target_state - perform_clean (action, const target&); - }; - } -} - -#endif // BUILD2_CLI_RULE diff --git a/build2/cli/rule.cxx b/build2/cli/rule.cxx index 2eb98c7..9e925b3 100644 --- a/build2/cli/rule.cxx +++ b/build2/cli/rule.cxx @@ -2,16 +2,16 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cli/rule.hxx b/build2/cli/rule.hxx new file mode 100644 index 0000000..6798a6b --- /dev/null +++ b/build2/cli/rule.hxx @@ -0,0 +1,39 @@ +// file : build2/cli/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_RULE_HXX +#define BUILD2_CLI_RULE_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cli + { + // @@ Redo as two separate rules? + // + class compile: public rule + { + public: + compile () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + static target_state + perform_clean (action, const target&); + }; + } +} + +#endif // BUILD2_CLI_RULE_HXX diff --git a/build2/cli/target b/build2/cli/target deleted file mode 100644 index 5e51bb4..0000000 --- a/build2/cli/target +++ /dev/null @@ -1,55 +0,0 @@ -// file : build2/cli/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CLI_TARGET -#define BUILD2_CLI_TARGET - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace cli - { - class cli: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Standard layout type compatible with group_view's const target*[3]. - // - struct cli_cxx_members - { - const cxx::hxx* h = nullptr; - const cxx::cxx* c = nullptr; - const cxx::ixx* i = nullptr; - }; - - class cli_cxx: public mtime_target, public cli_cxx_members - { - public: - using mtime_target::mtime_target; - - virtual group_view - group_members (action_type) const override; - - public: - static const target_type static_type; - - virtual const target_type& - dynamic_type () const override {return static_type;} - }; - } -} - -#endif // BUILD2_CLI_TARGET diff --git a/build2/cli/target.cxx b/build2/cli/target.cxx index 2199f79..0b85810 100644 --- a/build2/cli/target.cxx +++ b/build2/cli/target.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; using namespace butl; diff --git a/build2/cli/target.hxx b/build2/cli/target.hxx new file mode 100644 index 0000000..1247172 --- /dev/null +++ b/build2/cli/target.hxx @@ -0,0 +1,55 @@ +// file : build2/cli/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CLI_TARGET_HXX +#define BUILD2_CLI_TARGET_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace cli + { + class cli: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Standard layout type compatible with group_view's const target*[3]. + // + struct cli_cxx_members + { + const cxx::hxx* h = nullptr; + const cxx::cxx* c = nullptr; + const cxx::ixx* i = nullptr; + }; + + class cli_cxx: public mtime_target, public cli_cxx_members + { + public: + using mtime_target::mtime_target; + + virtual group_view + group_members (action_type) const override; + + public: + static const target_type static_type; + + virtual const target_type& + dynamic_type () const override {return static_type;} + }; + } +} + +#endif // BUILD2_CLI_TARGET_HXX diff --git a/build2/config/init b/build2/config/init deleted file mode 100644 index 7100ab2..0000000 --- a/build2/config/init +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/config/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CONFIG_INIT -#define BUILD2_CONFIG_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace config - { - void - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_CONFIG_INIT diff --git a/build2/config/init.cxx b/build2/config/init.cxx index 65aa7bb..889285d 100644 --- a/build2/config/init.cxx +++ b/build2/config/init.cxx @@ -2,18 +2,18 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#include -#include -#include -#include -#include // exists() -#include - -#include -#include -#include +#include + +#include +#include +#include +#include +#include // exists() +#include + +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/config/init.hxx b/build2/config/init.hxx new file mode 100644 index 0000000..45c288b --- /dev/null +++ b/build2/config/init.hxx @@ -0,0 +1,31 @@ +// file : build2/config/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_INIT_HXX +#define BUILD2_CONFIG_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace config + { + void + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_CONFIG_INIT_HXX diff --git a/build2/config/module b/build2/config/module deleted file mode 100644 index 3b58aa0..0000000 --- a/build2/config/module +++ /dev/null @@ -1,94 +0,0 @@ -// file : build2/config/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CONFIG_MODULE -#define BUILD2_CONFIG_MODULE - -#include -#include // find_if() - -#include - -#include -#include - -#include -#include - -namespace build2 -{ - namespace config - { - // An ordered list of modules each with an ordered list of list of - // config.* variables and their "save flags" (see save_variable()) that - // are used (as opposed to just being specified) in this configuration. - // Populated by the config utility functions (required(), optional()) - // and saved in the order populated. - // - struct saved_variable - { - reference_wrapper var; - uint64_t flags; - }; - - struct saved_variables: vector - { - // Normally each module only have a handful of config variables and we - // only do this during configuration so for now we do linear search - // instead of adding a map. - // - const_iterator - find (const variable& var) const - { - return std::find_if ( - begin (), - end (), - [&var] (const saved_variable& v) {return var == v.var;}); - } - }; - - struct saved_modules: butl::prefix_map - { - // Priority order with INT32_MIN being the highest. Modules with the - // same priority are saved in the order inserted. - // - // Generally, the idea is that we want higher-level modules at the top - // of the file since that's the configuration that we usualy want to - // change. So we have the following priority bands/defaults: - // - // 101-200/150 - code generators (e.g., yacc, bison) - // 201-300/250 - compilers (e.g., C, C++), - // 301-400/350 - binutils (ar, ld) - // - std::multimap order; - - iterator - insert (string name, int prio = 0) - { - auto p (emplace (move (name), saved_variables ())); - - if (p.second) - order.emplace (prio, p.first); - - return p.first; - } - }; - - struct module: module_base - { - config::saved_modules saved_modules; - - void - save_variable (const variable&, uint64_t flags = 0); - - void - save_module (const char* name, int prio = 0); - - static const string name; - static const uint64_t version; - }; - } -} - -#endif // BUILD2_CONFIG_MODULE diff --git a/build2/config/module.cxx b/build2/config/module.cxx index 6d54e46..19b5125 100644 --- a/build2/config/module.cxx +++ b/build2/config/module.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/config/module.hxx b/build2/config/module.hxx new file mode 100644 index 0000000..d997802 --- /dev/null +++ b/build2/config/module.hxx @@ -0,0 +1,94 @@ +// file : build2/config/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_MODULE_HXX +#define BUILD2_CONFIG_MODULE_HXX + +#include +#include // find_if() + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + namespace config + { + // An ordered list of modules each with an ordered list of list of + // config.* variables and their "save flags" (see save_variable()) that + // are used (as opposed to just being specified) in this configuration. + // Populated by the config utility functions (required(), optional()) + // and saved in the order populated. + // + struct saved_variable + { + reference_wrapper var; + uint64_t flags; + }; + + struct saved_variables: vector + { + // Normally each module only have a handful of config variables and we + // only do this during configuration so for now we do linear search + // instead of adding a map. + // + const_iterator + find (const variable& var) const + { + return std::find_if ( + begin (), + end (), + [&var] (const saved_variable& v) {return var == v.var;}); + } + }; + + struct saved_modules: butl::prefix_map + { + // Priority order with INT32_MIN being the highest. Modules with the + // same priority are saved in the order inserted. + // + // Generally, the idea is that we want higher-level modules at the top + // of the file since that's the configuration that we usualy want to + // change. So we have the following priority bands/defaults: + // + // 101-200/150 - code generators (e.g., yacc, bison) + // 201-300/250 - compilers (e.g., C, C++), + // 301-400/350 - binutils (ar, ld) + // + std::multimap order; + + iterator + insert (string name, int prio = 0) + { + auto p (emplace (move (name), saved_variables ())); + + if (p.second) + order.emplace (prio, p.first); + + return p.first; + } + }; + + struct module: module_base + { + config::saved_modules saved_modules; + + void + save_variable (const variable&, uint64_t flags = 0); + + void + save_module (const char* name, int prio = 0); + + static const string name; + static const uint64_t version; + }; + } +} + +#endif // BUILD2_CONFIG_MODULE_HXX diff --git a/build2/config/operation b/build2/config/operation deleted file mode 100644 index 425869c..0000000 --- a/build2/config/operation +++ /dev/null @@ -1,29 +0,0 @@ -// file : build2/config/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CONFIG_OPERATION -#define BUILD2_CONFIG_OPERATION - -#include -#include - -#include - -namespace build2 -{ - namespace config - { - extern const meta_operation_info configure; - extern const meta_operation_info disfigure; - - const string& - preprocess_create (const variable_overrides&, - values&, - vector_view&, - bool, - const location&); - } -} - -#endif // BUILD2_CONFIG_OPERATION diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index cf53292..bffc45b 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -2,21 +2,21 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include using namespace std; using namespace butl; diff --git a/build2/config/operation.hxx b/build2/config/operation.hxx new file mode 100644 index 0000000..bf9403f --- /dev/null +++ b/build2/config/operation.hxx @@ -0,0 +1,29 @@ +// file : build2/config/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_OPERATION_HXX +#define BUILD2_CONFIG_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace config + { + extern const meta_operation_info configure; + extern const meta_operation_info disfigure; + + const string& + preprocess_create (const variable_overrides&, + values&, + vector_view&, + bool, + const location&); + } +} + +#endif // BUILD2_CONFIG_OPERATION_HXX diff --git a/build2/config/utility b/build2/config/utility deleted file mode 100644 index 041277a..0000000 --- a/build2/config/utility +++ /dev/null @@ -1,152 +0,0 @@ -// file : build2/config/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CONFIG_UTILITY -#define BUILD2_CONFIG_UTILITY - -#include -#include - -#include -#include - -namespace build2 -{ - class scope; - - namespace config - { - // Set, if necessary, a required config.* variable. - // - // If override is true and the variable doesn't come from this root scope - // or from the command line (i.e., it is inherited from the amalgamtion), - // then its value is "overridden" to the default value on this root scope. - // See save_variable() for more information on save_flags. - // - // Return the reference to the value as well as the indication of whether - // the value is "new", that is, it was set to the default value (inherited - // or not, including overrides). We also treat command line overrides - // (inherited or not) as new. This flag is usually used to test that the - // new value is valid, print report, etc. We return the value as lookup - // (always defined) to pass alone its location (could be used to detect - // inheritance, etc). - // - // Note also that if save_flags has save_commented, then a default value - // is never considered "new" since for such variables absence of a value - // means the default value. - // - template - pair - required (scope& root, - const variable&, - const T& default_value, - bool override = false, - uint64_t save_flags = 0); - - // Note that the variable is expected to have already been registered. - // - template - inline pair - required (scope& root, - const string& name, - const T& default_value, - bool override = false, - uint64_t save_flags = 0) - { - return required ( - root, var_pool[name], default_value, override, save_flags); - } - - inline pair - required (scope& root, - const string& name, - const char* default_value, - bool override = false, - uint64_t save_flags = 0) - { - return required ( - root, name, string (default_value), override, save_flags); - } - - // As above, but leave the unspecified value as undefined rather than - // setting it to the default value. - // - // This can be useful when we don't have a default value but may figure - // out some fallback. See config.bin.target for an example. - // - pair - omitted (scope& root, const variable&); - - // Note that the variable is expected to have already been registered. - // - inline pair - omitted (scope& root, const string& name) - { - return omitted (root, var_pool[name]); - } - - // Set, if necessary, an optional config.* variable. In particular, an - // unspecified variable is set to NULL which is used to distinguish - // between the "configured as unspecified" and "not yet configured" cases. - // - // Return the value (as always defined lookup), which can be NULL. - // - lookup - optional (scope& root, const variable&); - - // Note that the variable is expected to have already been registered. - // - inline lookup - optional (scope& root, const string& name) - { - return optional (root, var_pool[name]); - } - - // Check whether there are any variables specified from the config - // namespace. The idea is that we can check if there are any, say, - // config.install.* values. If there are none, then we can assume - // this functionality is not (yet) used and omit writing a whole - // bunch of NULL config.install.* values to the config.build file. - // We call it omitted/delayed configuration. - // - // Note that this function detects and ignores the special - // config.*.configured variable which may be used by a module to - // "remember" that it is unconfigured (e.g., in order to avoid re- - // running the tests, etc). - // - bool - specified (scope& root, const string& name); - - // Check if there is a false config.*.configured value. This mechanism can - // be used to "remember" that the module is left unconfigured in order to - // avoid re-running the tests, etc. - // - bool - unconfigured (scope& root, const string& name); - - // Set the config.*.configured value. Note that you only need to set it to - // false. It will be automatically ignored if there are any other config.* - // values for this module. Return true if this sets a new value. - // - bool - unconfigured (scope& root, const string& name, bool); - - // Enter the variable so that it is saved during configuration. See - // config::module for details. - // - const uint64_t save_commented = 0x01; // Save default value as commented. - - void - save_variable (scope& root, const variable&, uint64_t flags = 0); - - // Establish module order/priority. See config::module for details. - // - void - save_module (scope& root, const char* name, int prio = 0); - } -} - -#include - -#endif // BUILD2_CONFIG_UTILITY diff --git a/build2/config/utility.cxx b/build2/config/utility.cxx index ea66e92..c195e0b 100644 --- a/build2/config/utility.cxx +++ b/build2/config/utility.cxx @@ -2,11 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include +#include -#include +#include using namespace std; diff --git a/build2/config/utility.hxx b/build2/config/utility.hxx new file mode 100644 index 0000000..33a3985 --- /dev/null +++ b/build2/config/utility.hxx @@ -0,0 +1,152 @@ +// file : build2/config/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONFIG_UTILITY_HXX +#define BUILD2_CONFIG_UTILITY_HXX + +#include +#include + +#include +#include + +namespace build2 +{ + class scope; + + namespace config + { + // Set, if necessary, a required config.* variable. + // + // If override is true and the variable doesn't come from this root scope + // or from the command line (i.e., it is inherited from the amalgamtion), + // then its value is "overridden" to the default value on this root scope. + // See save_variable() for more information on save_flags. + // + // Return the reference to the value as well as the indication of whether + // the value is "new", that is, it was set to the default value (inherited + // or not, including overrides). We also treat command line overrides + // (inherited or not) as new. This flag is usually used to test that the + // new value is valid, print report, etc. We return the value as lookup + // (always defined) to pass alone its location (could be used to detect + // inheritance, etc). + // + // Note also that if save_flags has save_commented, then a default value + // is never considered "new" since for such variables absence of a value + // means the default value. + // + template + pair + required (scope& root, + const variable&, + const T& default_value, + bool override = false, + uint64_t save_flags = 0); + + // Note that the variable is expected to have already been registered. + // + template + inline pair + required (scope& root, + const string& name, + const T& default_value, + bool override = false, + uint64_t save_flags = 0) + { + return required ( + root, var_pool[name], default_value, override, save_flags); + } + + inline pair + required (scope& root, + const string& name, + const char* default_value, + bool override = false, + uint64_t save_flags = 0) + { + return required ( + root, name, string (default_value), override, save_flags); + } + + // As above, but leave the unspecified value as undefined rather than + // setting it to the default value. + // + // This can be useful when we don't have a default value but may figure + // out some fallback. See config.bin.target for an example. + // + pair + omitted (scope& root, const variable&); + + // Note that the variable is expected to have already been registered. + // + inline pair + omitted (scope& root, const string& name) + { + return omitted (root, var_pool[name]); + } + + // Set, if necessary, an optional config.* variable. In particular, an + // unspecified variable is set to NULL which is used to distinguish + // between the "configured as unspecified" and "not yet configured" cases. + // + // Return the value (as always defined lookup), which can be NULL. + // + lookup + optional (scope& root, const variable&); + + // Note that the variable is expected to have already been registered. + // + inline lookup + optional (scope& root, const string& name) + { + return optional (root, var_pool[name]); + } + + // Check whether there are any variables specified from the config + // namespace. The idea is that we can check if there are any, say, + // config.install.* values. If there are none, then we can assume + // this functionality is not (yet) used and omit writing a whole + // bunch of NULL config.install.* values to the config.build file. + // We call it omitted/delayed configuration. + // + // Note that this function detects and ignores the special + // config.*.configured variable which may be used by a module to + // "remember" that it is unconfigured (e.g., in order to avoid re- + // running the tests, etc). + // + bool + specified (scope& root, const string& name); + + // Check if there is a false config.*.configured value. This mechanism can + // be used to "remember" that the module is left unconfigured in order to + // avoid re-running the tests, etc. + // + bool + unconfigured (scope& root, const string& name); + + // Set the config.*.configured value. Note that you only need to set it to + // false. It will be automatically ignored if there are any other config.* + // values for this module. Return true if this sets a new value. + // + bool + unconfigured (scope& root, const string& name, bool); + + // Enter the variable so that it is saved during configuration. See + // config::module for details. + // + const uint64_t save_commented = 0x01; // Save default value as commented. + + void + save_variable (scope& root, const variable&, uint64_t flags = 0); + + // Establish module order/priority. See config::module for details. + // + void + save_module (scope& root, const char* name, int prio = 0); + } +} + +#include + +#endif // BUILD2_CONFIG_UTILITY_HXX diff --git a/build2/config/utility.txx b/build2/config/utility.txx index 1434406..fc21710 100644 --- a/build2/config/utility.txx +++ b/build2/config/utility.txx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include namespace build2 { diff --git a/build2/context b/build2/context deleted file mode 100644 index bc73d5b..0000000 --- a/build2/context +++ /dev/null @@ -1,399 +0,0 @@ -// file : build2/context -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CONTEXT -#define BUILD2_CONTEXT - -#include -#include - -#include -#include -#include -#include - -namespace build2 -{ - // Main (and only) scheduler. Started up and shut down in main(). - // - extern scheduler sched; - - // In order to perform each operation the build system goes through the - // following phases: - // - // load - load the buildfiles - // match - search prerequisites and match rules - // execute - execute the matched rule - // - // The build system starts with a "serial load" phase and then continues - // with parallel search and execute. Match, however, can be interrupted - // both with load and execute. - // - // Match can be interrupted with "exclusive load" in order to load - // additional buildfiles. Similarly, it can be interrupted with (parallel) - // execute in order to build targetd required to complete the match (for - // example, generated source code or source code generators themselves. - // - // Such interruptions are performed by phase change that is protected by - // phase_mutex (which is also used to synchronize the state changes between - // phases). - // - // Serial load can perform arbitrary changes to the model. Exclusive load, - // however, can only perform "island appends". That is, it can create new - // "nodes" (variables, scopes, etc) but not change already existing nodes or - // invalidate any references to such (the idea here is that one should be - // able to load additional buildfiles as long as they don't interfere with - // the existing build state). The "islands" are identified by the - // load_generation number (0 for initial/serial load). It is incremented in - // case of a phase switch and is stored in various "nodes" (variables, etc) - // to allow modifications "within the islands". - // - extern run_phase phase; - extern size_t load_generation; - - // A "tri-mutex" that keeps all the threads in one of the three phases. When - // a thread wants to switch a phase, it has to wait for all the other - // threads to do the same (or release their phase locks). The load phase is - // exclusive. - // - // The interleaving match and execute is interesting: during match we read - // the "external state" (e.g., filesystem entries, modifications times, etc) - // and capture it in the "internal state" (our dependency graph). During - // execute we are modifying the external state with controlled modifications - // of the internal state to reflect the changes (e.g., update mtimes). If - // you think about it, it's pretty clear that we cannot safely perform both - // of these actions simultaneously. A good example would be running a code - // generator and header dependency extraction simultaneously: the extraction - // process may pick up headers as they are being generated. As a result, we - // either have everyone treat the external state as read-only or write-only. - // - class phase_mutex - { - public: - // Acquire a phase lock potentially blocking (unless already in the - // desired phase) until switching to the desired phase is possible. - // - void - lock (run_phase); - - // Release the phase lock potentially allowing (unless there are other - // locks on this phase) switching to a different phase. - // - void - unlock (run_phase); - - // Switch from one phase to another. Semantically, just unlock() followed - // by lock() but more efficient. - // - void - relock (run_phase unlock, run_phase lock); - - private: - friend struct phase_lock; - friend struct phase_unlock; - friend struct phase_switch; - - phase_mutex (): lc_ (0), mc_ (0), ec_ (0) {phase = run_phase::load;} - - static phase_mutex instance; - - private: - // We have a counter for each phase which represents the number of threads - // in or waiting for this phase. - // - // We use condition variables to wait for a phase switch. The load phase - // is exclusive so we have a separate mutex to serialize it (think of it - // as a second level locking). - // - // When the mutex is unlocked (all three counters become zero, the phase - // is always changed to load (this is also the initial state). - // - mutex m_; - size_t lc_; - size_t mc_; - size_t ec_; - - condition_variable lv_; - condition_variable mv_; - condition_variable ev_; - - mutex lm_; - }; - - // Grab a new phase lock releasing it on destruction. The lock can be - // "owning" or "referencing" (recursive). - // - // On the referencing semantics: If there is already an instance of - // phase_lock in this thread, then the new instance simply references it. - // - // The reason for this semantics is to support the following scheduling - // pattern (in actual code we use wait_guard to RAII it): - // - // atomic_count task_count (0); - // - // { - // phase_lock l (run_phase::match); // (1) - // - // for (...) - // { - // sched.async (task_count, - // [] (...) - // { - // phase_lock pl (run_phase::match); // (2) - // ... - // }, - // ...); - // } - // } - // - // sched.wait (task_count); // (3) - // - // Here is what's going on here: - // - // 1. We first get a phase lock "for ourselves" since after the first - // iteration of the loop, things may become asynchronous (including - // attempts to switch the phase and modify the structure we are iteration - // upon). - // - // 2. The task can be queued or it can be executed synchronously inside - // async() (refer to the scheduler class for details on this semantics). - // - // If this is an async()-synchronous execution, then the task will create - // a referencing phase_lock. If, however, this is a queued execution - // (including wait()-synchronous), then the task will create a top-level - // phase_lock. - // - // Note that we only acquire the lock once the task starts executing - // (there is no reason to hold the lock while the task is sitting in the - // queue). This optimization assumes that whatever else we pass to the - // task (for example, a reference to a target) is stable (in other words, - // such a reference cannot become invalid). - // - // 3. Before calling wait(), we release our phase lock to allow switching - // the phase. - // - struct phase_lock - { - explicit phase_lock (run_phase); - ~phase_lock (); - - phase_lock (phase_lock&&) = delete; - phase_lock (const phase_lock&) = delete; - - phase_lock& operator= (phase_lock&&) = delete; - phase_lock& operator= (const phase_lock&) = delete; - - run_phase p; - - static -#ifdef __cpp_thread_local - thread_local -#else - __thread -#endif - phase_lock* instance; - }; - - // Assuming we have a lock on the current phase, temporarily release it - // and reacquire on destruction. - // - struct phase_unlock - { - phase_unlock (bool unlock = true); - ~phase_unlock (); - - phase_lock* l; - }; - - // Assuming we have a lock on the current phase, temporarily switch to a - // new phase and switch back on destruction. - // - struct phase_switch - { - explicit phase_switch (run_phase); - ~phase_switch (); - - run_phase o, n; - }; - - // Wait for a task count optionally and temporarily unlocking the phase. - // - struct wait_guard - { - ~wait_guard () noexcept (false); - - explicit - wait_guard (atomic_count& task_count, - bool phase = false); - - wait_guard (size_t start_count, - atomic_count& task_count, - bool phase = false); - - void - wait (); - - size_t start_count; - atomic_count* task_count; - bool phase; - }; - - // Cached variables. - // - extern const variable* var_src_root; - extern const variable* var_out_root; - extern const variable* var_src_base; - extern const variable* var_out_base; - - extern const variable* var_project; - extern const variable* var_amalgamation; - extern const variable* var_subprojects; - - extern const variable* var_import_target; // import.target - - // Current action (meta/operation). - // - // The names unlike info are available during boot but may not yet be - // lifted. The name is always for an outer operation (or meta operation - // that hasn't been recognized as such yet). - // - extern const string* current_mname; - extern const string* current_oname; - - extern const meta_operation_info* current_mif; - extern const operation_info* current_inner_oif; - extern const operation_info* current_outer_oif; - extern size_t current_on; // Current operation number (1-based) in the - // meta-operation batch. - - extern execution_mode current_mode; - - // Total number of dependency relationships in the current action. Together - // with the target::dependents count it is incremented during the rule - // search & match phase and is decremented during execution with the - // expectation of it reaching 0. Used as a sanity check. - // - extern atomic_count dependency_count; - - inline void - set_current_mif (const meta_operation_info& mif) - { - current_mname = &mif.name; - current_mif = &mif; - current_on = 0; // Reset. - } - - inline void - set_current_oif (const operation_info& inner_oif, - const operation_info* outer_oif = nullptr) - { - current_oname = &(outer_oif == nullptr ? inner_oif : *outer_oif).name; - current_inner_oif = &inner_oif; - current_outer_oif = outer_oif; - current_on++; - current_mode = inner_oif.mode; - dependency_count.store (0, memory_order_relaxed); // Serial. - } - - // Keep going flag. - // - // Note that setting it to false is not of much help unless we are running - // serially. In parallel we queue most of the things up before we see any - // failures. - // - extern bool keep_going; - - // Reset the build state. In particular, this removes all the targets, - // scopes, and variables. - // - variable_overrides - reset (const strings& cmd_vars); - - // Return the project name or empty string if unnamed. - // - inline const string& - project (const scope& root) - { - auto l (root[var_project]); - return l ? cast (l) : empty_string; - } - - // Return the src/out directory corresponding to the given out/src. The - // passed directory should be a sub-directory of out/src_root. - // - dir_path - src_out (const dir_path& out, const scope& root); - - dir_path - src_out (const dir_path& out, - const dir_path& out_root, const dir_path& src_root); - - dir_path - out_src (const dir_path& src, const scope& root); - - dir_path - out_src (const dir_path& src, - const dir_path& out_root, const dir_path& src_root); - - // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}", - // and "updating exe{foo} is configured". Use like this: - // - // info << "while " << diag_doing (a, t); - // - class target; - - struct diag_phrase - { - const action& a; - const target& t; - void (*f) (ostream&, const action&, const target&); - }; - - inline ostream& - operator<< (ostream& os, const diag_phrase& p) - { - p.f (os, p.a, p.t); - return os; - } - - void - diag_do (ostream&, const action&, const target&); - - inline diag_phrase - diag_do (const action& a, const target& t) - { - return diag_phrase {a, t, &diag_do}; - } - - void - diag_doing (ostream&, const action&, const target&); - - inline diag_phrase - diag_doing (const action& a, const target& t) - { - return diag_phrase {a, t, &diag_doing}; - } - - void - diag_did (ostream&, const action&, const target&); - - inline diag_phrase - diag_did (const action& a, const target& t) - { - return diag_phrase {a, t, &diag_did}; - } - - void - diag_done (ostream&, const action&, const target&); - - inline diag_phrase - diag_done (const action& a, const target& t) - { - return diag_phrase {a, t, &diag_done}; - } -} - -#include - -#endif // BUILD2_CONTEXT diff --git a/build2/context.cxx b/build2/context.cxx index 56db9d4..669fcae 100644 --- a/build2/context.cxx +++ b/build2/context.cxx @@ -2,22 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include // For command line variable parsing. // -#include -#include -#include +#include +#include +#include -#include // config::preprocess_create(). +#include // config::preprocess_create(). using namespace std; using namespace butl; @@ -220,7 +220,7 @@ namespace build2 vp.clear (); // Reset meta/operation tables. Note that the order should match the id - // constants in . + // constants in . // meta_operation_table.clear (); meta_operation_table.insert ("noop"); @@ -240,7 +240,7 @@ namespace build2 operation_table.insert ("uninstall"); // Create global scope. Note that the empty path is a prefix for any other - // path. See the comment in for details. + // path. See the comment in for details. // auto make_global_scope = [&sm] () -> scope& { diff --git a/build2/context.hxx b/build2/context.hxx new file mode 100644 index 0000000..ff8b5b3 --- /dev/null +++ b/build2/context.hxx @@ -0,0 +1,399 @@ +// file : build2/context.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CONTEXT_HXX +#define BUILD2_CONTEXT_HXX + +#include +#include + +#include +#include +#include +#include + +namespace build2 +{ + // Main (and only) scheduler. Started up and shut down in main(). + // + extern scheduler sched; + + // In order to perform each operation the build system goes through the + // following phases: + // + // load - load the buildfiles + // match - search prerequisites and match rules + // execute - execute the matched rule + // + // The build system starts with a "serial load" phase and then continues + // with parallel search and execute. Match, however, can be interrupted + // both with load and execute. + // + // Match can be interrupted with "exclusive load" in order to load + // additional buildfiles. Similarly, it can be interrupted with (parallel) + // execute in order to build targetd required to complete the match (for + // example, generated source code or source code generators themselves. + // + // Such interruptions are performed by phase change that is protected by + // phase_mutex (which is also used to synchronize the state changes between + // phases). + // + // Serial load can perform arbitrary changes to the model. Exclusive load, + // however, can only perform "island appends". That is, it can create new + // "nodes" (variables, scopes, etc) but not change already existing nodes or + // invalidate any references to such (the idea here is that one should be + // able to load additional buildfiles as long as they don't interfere with + // the existing build state). The "islands" are identified by the + // load_generation number (0 for initial/serial load). It is incremented in + // case of a phase switch and is stored in various "nodes" (variables, etc) + // to allow modifications "within the islands". + // + extern run_phase phase; + extern size_t load_generation; + + // A "tri-mutex" that keeps all the threads in one of the three phases. When + // a thread wants to switch a phase, it has to wait for all the other + // threads to do the same (or release their phase locks). The load phase is + // exclusive. + // + // The interleaving match and execute is interesting: during match we read + // the "external state" (e.g., filesystem entries, modifications times, etc) + // and capture it in the "internal state" (our dependency graph). During + // execute we are modifying the external state with controlled modifications + // of the internal state to reflect the changes (e.g., update mtimes). If + // you think about it, it's pretty clear that we cannot safely perform both + // of these actions simultaneously. A good example would be running a code + // generator and header dependency extraction simultaneously: the extraction + // process may pick up headers as they are being generated. As a result, we + // either have everyone treat the external state as read-only or write-only. + // + class phase_mutex + { + public: + // Acquire a phase lock potentially blocking (unless already in the + // desired phase) until switching to the desired phase is possible. + // + void + lock (run_phase); + + // Release the phase lock potentially allowing (unless there are other + // locks on this phase) switching to a different phase. + // + void + unlock (run_phase); + + // Switch from one phase to another. Semantically, just unlock() followed + // by lock() but more efficient. + // + void + relock (run_phase unlock, run_phase lock); + + private: + friend struct phase_lock; + friend struct phase_unlock; + friend struct phase_switch; + + phase_mutex (): lc_ (0), mc_ (0), ec_ (0) {phase = run_phase::load;} + + static phase_mutex instance; + + private: + // We have a counter for each phase which represents the number of threads + // in or waiting for this phase. + // + // We use condition variables to wait for a phase switch. The load phase + // is exclusive so we have a separate mutex to serialize it (think of it + // as a second level locking). + // + // When the mutex is unlocked (all three counters become zero, the phase + // is always changed to load (this is also the initial state). + // + mutex m_; + size_t lc_; + size_t mc_; + size_t ec_; + + condition_variable lv_; + condition_variable mv_; + condition_variable ev_; + + mutex lm_; + }; + + // Grab a new phase lock releasing it on destruction. The lock can be + // "owning" or "referencing" (recursive). + // + // On the referencing semantics: If there is already an instance of + // phase_lock in this thread, then the new instance simply references it. + // + // The reason for this semantics is to support the following scheduling + // pattern (in actual code we use wait_guard to RAII it): + // + // atomic_count task_count (0); + // + // { + // phase_lock l (run_phase::match); // (1) + // + // for (...) + // { + // sched.async (task_count, + // [] (...) + // { + // phase_lock pl (run_phase::match); // (2) + // ... + // }, + // ...); + // } + // } + // + // sched.wait (task_count); // (3) + // + // Here is what's going on here: + // + // 1. We first get a phase lock "for ourselves" since after the first + // iteration of the loop, things may become asynchronous (including + // attempts to switch the phase and modify the structure we are iteration + // upon). + // + // 2. The task can be queued or it can be executed synchronously inside + // async() (refer to the scheduler class for details on this semantics). + // + // If this is an async()-synchronous execution, then the task will create + // a referencing phase_lock. If, however, this is a queued execution + // (including wait()-synchronous), then the task will create a top-level + // phase_lock. + // + // Note that we only acquire the lock once the task starts executing + // (there is no reason to hold the lock while the task is sitting in the + // queue). This optimization assumes that whatever else we pass to the + // task (for example, a reference to a target) is stable (in other words, + // such a reference cannot become invalid). + // + // 3. Before calling wait(), we release our phase lock to allow switching + // the phase. + // + struct phase_lock + { + explicit phase_lock (run_phase); + ~phase_lock (); + + phase_lock (phase_lock&&) = delete; + phase_lock (const phase_lock&) = delete; + + phase_lock& operator= (phase_lock&&) = delete; + phase_lock& operator= (const phase_lock&) = delete; + + run_phase p; + + static +#ifdef __cpp_thread_local + thread_local +#else + __thread +#endif + phase_lock* instance; + }; + + // Assuming we have a lock on the current phase, temporarily release it + // and reacquire on destruction. + // + struct phase_unlock + { + phase_unlock (bool unlock = true); + ~phase_unlock (); + + phase_lock* l; + }; + + // Assuming we have a lock on the current phase, temporarily switch to a + // new phase and switch back on destruction. + // + struct phase_switch + { + explicit phase_switch (run_phase); + ~phase_switch (); + + run_phase o, n; + }; + + // Wait for a task count optionally and temporarily unlocking the phase. + // + struct wait_guard + { + ~wait_guard () noexcept (false); + + explicit + wait_guard (atomic_count& task_count, + bool phase = false); + + wait_guard (size_t start_count, + atomic_count& task_count, + bool phase = false); + + void + wait (); + + size_t start_count; + atomic_count* task_count; + bool phase; + }; + + // Cached variables. + // + extern const variable* var_src_root; + extern const variable* var_out_root; + extern const variable* var_src_base; + extern const variable* var_out_base; + + extern const variable* var_project; + extern const variable* var_amalgamation; + extern const variable* var_subprojects; + + extern const variable* var_import_target; // import.target + + // Current action (meta/operation). + // + // The names unlike info are available during boot but may not yet be + // lifted. The name is always for an outer operation (or meta operation + // that hasn't been recognized as such yet). + // + extern const string* current_mname; + extern const string* current_oname; + + extern const meta_operation_info* current_mif; + extern const operation_info* current_inner_oif; + extern const operation_info* current_outer_oif; + extern size_t current_on; // Current operation number (1-based) in the + // meta-operation batch. + + extern execution_mode current_mode; + + // Total number of dependency relationships in the current action. Together + // with the target::dependents count it is incremented during the rule + // search & match phase and is decremented during execution with the + // expectation of it reaching 0. Used as a sanity check. + // + extern atomic_count dependency_count; + + inline void + set_current_mif (const meta_operation_info& mif) + { + current_mname = &mif.name; + current_mif = &mif; + current_on = 0; // Reset. + } + + inline void + set_current_oif (const operation_info& inner_oif, + const operation_info* outer_oif = nullptr) + { + current_oname = &(outer_oif == nullptr ? inner_oif : *outer_oif).name; + current_inner_oif = &inner_oif; + current_outer_oif = outer_oif; + current_on++; + current_mode = inner_oif.mode; + dependency_count.store (0, memory_order_relaxed); // Serial. + } + + // Keep going flag. + // + // Note that setting it to false is not of much help unless we are running + // serially. In parallel we queue most of the things up before we see any + // failures. + // + extern bool keep_going; + + // Reset the build state. In particular, this removes all the targets, + // scopes, and variables. + // + variable_overrides + reset (const strings& cmd_vars); + + // Return the project name or empty string if unnamed. + // + inline const string& + project (const scope& root) + { + auto l (root[var_project]); + return l ? cast (l) : empty_string; + } + + // Return the src/out directory corresponding to the given out/src. The + // passed directory should be a sub-directory of out/src_root. + // + dir_path + src_out (const dir_path& out, const scope& root); + + dir_path + src_out (const dir_path& out, + const dir_path& out_root, const dir_path& src_root); + + dir_path + out_src (const dir_path& src, const scope& root); + + dir_path + out_src (const dir_path& src, + const dir_path& out_root, const dir_path& src_root); + + // Action phrases, e.g., "configure update exe{foo}", "updating exe{foo}", + // and "updating exe{foo} is configured". Use like this: + // + // info << "while " << diag_doing (a, t); + // + class target; + + struct diag_phrase + { + const action& a; + const target& t; + void (*f) (ostream&, const action&, const target&); + }; + + inline ostream& + operator<< (ostream& os, const diag_phrase& p) + { + p.f (os, p.a, p.t); + return os; + } + + void + diag_do (ostream&, const action&, const target&); + + inline diag_phrase + diag_do (const action& a, const target& t) + { + return diag_phrase {a, t, &diag_do}; + } + + void + diag_doing (ostream&, const action&, const target&); + + inline diag_phrase + diag_doing (const action& a, const target& t) + { + return diag_phrase {a, t, &diag_doing}; + } + + void + diag_did (ostream&, const action&, const target&); + + inline diag_phrase + diag_did (const action& a, const target& t) + { + return diag_phrase {a, t, &diag_did}; + } + + void + diag_done (ostream&, const action&, const target&); + + inline diag_phrase + diag_done (const action& a, const target& t) + { + return diag_phrase {a, t, &diag_done}; + } +} + +#include + +#endif // BUILD2_CONTEXT_HXX diff --git a/build2/cxx/init b/build2/cxx/init deleted file mode 100644 index 78798b9..0000000 --- a/build2/cxx/init +++ /dev/null @@ -1,37 +0,0 @@ -// file : build2/cxx/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CXX_INIT -#define BUILD2_CXX_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace cxx - { - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_CXX_INIT diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index f76779b..c74a077 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -2,16 +2,16 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include +#include +#include +#include -#include -#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/cxx/init.hxx b/build2/cxx/init.hxx new file mode 100644 index 0000000..5ba4041 --- /dev/null +++ b/build2/cxx/init.hxx @@ -0,0 +1,37 @@ +// file : build2/cxx/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_INIT_HXX +#define BUILD2_CXX_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace cxx + { + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_CXX_INIT_HXX diff --git a/build2/cxx/target b/build2/cxx/target deleted file mode 100644 index 6a63060..0000000 --- a/build2/cxx/target +++ /dev/null @@ -1,63 +0,0 @@ -// file : build2/cxx/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_CXX_TARGET -#define BUILD2_CXX_TARGET - -#include -#include - -#include -#include - -namespace build2 -{ - namespace cxx - { - using cc::h; - using cc::c; - - class hxx: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class ixx: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class txx: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class cxx: public cc::cc - { - public: - using cc::cc; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - } -} - -#endif // BUILD2_CXX_TARGET diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx index 8955cb2..7fb9971 100644 --- a/build2/cxx/target.cxx +++ b/build2/cxx/target.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/cxx/target.hxx b/build2/cxx/target.hxx new file mode 100644 index 0000000..4dd93e3 --- /dev/null +++ b/build2/cxx/target.hxx @@ -0,0 +1,63 @@ +// file : build2/cxx/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_CXX_TARGET_HXX +#define BUILD2_CXX_TARGET_HXX + +#include +#include + +#include +#include + +namespace build2 +{ + namespace cxx + { + using cc::h; + using cc::c; + + class hxx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class ixx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class txx: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class cxx: public cc::cc + { + public: + using cc::cc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_CXX_TARGET_HXX diff --git a/build2/depdb b/build2/depdb deleted file mode 100644 index abd2e20..0000000 --- a/build2/depdb +++ /dev/null @@ -1,201 +0,0 @@ -// file : build2/depdb -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DEPDB -#define BUILD2_DEPDB - -#include -#include // strlen() - -#include -#include - -namespace build2 -{ - // Auxiliary dependency database (those .d files). Uses io_error and - // system_error exceptions to signal errors. - // - // This is a strange beast: a line-oriented, streaming database that can, at - // some point, be switched from reading to (over)writing. The idea is to - // store auxiliary/ad-hoc dependency information in the "invalidation" - // order. That is, if an earlier line is out of date, then all the - // subsequent ones are out of date as well. - // - // As an example, consider a dependency database for foo.o which is built - // from foo.cxx by the cxx.compile rule. The first line could be the rule - // name itself (perhaps with the version). If a different rule is now - // building foo.o, then any dep info that was saved by cxx.compile is - // probably useless. Next we can have the command line options that were - // used to build foo.o. Then could come the source file name followed by the - // extracted header dependencies. If the compile options or the source file - // name have changed, then the header dependencies are likely to have - // changed as well. - // - // As an example, here is what our foo.o.d could look like (the first line - // is the database format version and the last '\0' character is the end - // marker): - // - // 1 - // cxx.compile 1 - // g++-4.8 -I/tmp/foo -O3 - // /tmp/foo/foo.cxx - // /tmp/foo/foo.hxx - // /usr/include/string.h - // /usr/include/stdlib.h - // /tmp/foo/bar.hxx - // ^@ - // - // A race is possible between updating the database and the target. For - // example, we may detect a line mismatch that renders the target out-of- - // date (say, compile options in the above example). We update the database - // but before getting a chance to update the target, we get interrupted. On - // a subsequent re-run, because the database has been updated, we will miss - // the "target requires update" condition. - // - // If we assume that an update of the database also means an update of the - // target, then this "interrupted update" situation can be easily detected - // by comparing the database and target modification timestamps. - // - class depdb - { - public: - // Open the database for reading. Note that if the file does not exist, - // has wrong format version, or is corrupt, then the database will be - // immediately switched to writing. - // - depdb (const path&); - - // Return the modification time of the database. This value only makes - // sense while reading (in the write mode it will be timestamp_unknown). - // - timestamp - mtime () const {return mtime_;} - - // Update the database modification time in close() even if otherwise - // no modifications are necessary (i.e., the database is in the read - // mode and is at eof). - // - void - touch () {touch_ = true;} - - // Close the database. If this function is not called, then the database - // may be left in the old/currupt state. Note that in the read mode this - // function will "chop off" lines that haven't been read. - // - void - close (); - - // Read the next line. If the result is not NULL, then it is a pointer to - // the next line in the database (which you are free to move from). If you - // then call write(), this line will be overwritten. - // - // If the result is NULL, then it means no next line is available. This - // can be due to several reasons: - // - // - eof reached (you can detect this by calling more() before read()) - // - database is already in the write mode - // - the next line (and the rest of the database are corrupt) - // - string* - read () {return state_ == state::write ? nullptr : read_ ();} - - // Return true if the database is in the read mode and there is at least - // one more line available. Note that there is no guarantee that the line - // is not corrupt. In other words, read() can still return NULL, it just - // won't be because of eof. - // - bool - more () {return state_ == state::read;} - - bool - reading () {return state_ != state::write;} - - bool - writing () {return state_ == state::write;} - - // Write the next line. Note that this switches the database into the - // write mode and no further reading will be possible. - // - void - write (const string& l) {write (l.c_str (), l.size ());} - - void - write (const path& p) {write (p.string ());} - - void - write (const char* s) {write (s, std::strlen (s));} - - void - write (const char*, size_t); - - void - write (char); - - // Read the next line and compare it to the expected value. If it matches, - // return NULL. Otherwise, overwrite it and return the old value (which - // could also be NULL). This strange-sounding result semantics is used to - // detect the "there is a value but it does not match" case for tracing: - // - // if (string* o = d.expect (...)) - // l4 ([&]{trace << "X mismatch forcing update of " << t;}); - // - string* - expect (const string& v) - { - string* l (read ()); - if (l == nullptr || *l != v) - { - write (v); - return l; - } - - return nullptr; - } - - string* - expect (const path& v) - { - string* l (read ()); - if (l == nullptr || path::traits::compare (*l, v.string ()) != 0) - { - write (v); - return l; - } - - return nullptr; - } - - string* - expect (const char* v) - { - string* l (read ()); - if (l == nullptr || *l != v) - { - write (v); - return l; - } - - return nullptr; - } - - private: - void - change (bool flush = true); - - string* - read_ (); - - private: - timestamp mtime_; - std::fstream fs_; - - std::fstream::pos_type pos_; // Start of the last returned line. - string line_; - - enum class state {read, read_eof, write} state_; - bool touch_; - }; -} - -#endif // BUILD2_DEPDB diff --git a/build2/depdb.cxx b/build2/depdb.cxx index 60425d5..5aa2970 100644 --- a/build2/depdb.cxx +++ b/build2/depdb.cxx @@ -2,9 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // file_mtime() +#include // file_mtime() using namespace std; using namespace butl; diff --git a/build2/depdb.hxx b/build2/depdb.hxx new file mode 100644 index 0000000..9dffa28 --- /dev/null +++ b/build2/depdb.hxx @@ -0,0 +1,201 @@ +// file : build2/depdb.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DEPDB_HXX +#define BUILD2_DEPDB_HXX + +#include +#include // strlen() + +#include +#include + +namespace build2 +{ + // Auxiliary dependency database (those .d files). Uses io_error and + // system_error exceptions to signal errors. + // + // This is a strange beast: a line-oriented, streaming database that can, at + // some point, be switched from reading to (over)writing. The idea is to + // store auxiliary/ad-hoc dependency information in the "invalidation" + // order. That is, if an earlier line is out of date, then all the + // subsequent ones are out of date as well. + // + // As an example, consider a dependency database for foo.o which is built + // from foo.cxx by the cxx.compile rule. The first line could be the rule + // name itself (perhaps with the version). If a different rule is now + // building foo.o, then any dep info that was saved by cxx.compile is + // probably useless. Next we can have the command line options that were + // used to build foo.o. Then could come the source file name followed by the + // extracted header dependencies. If the compile options or the source file + // name have changed, then the header dependencies are likely to have + // changed as well. + // + // As an example, here is what our foo.o.d could look like (the first line + // is the database format version and the last '\0' character is the end + // marker): + // + // 1 + // cxx.compile 1 + // g++-4.8 -I/tmp/foo -O3 + // /tmp/foo/foo.cxx + // /tmp/foo/foo.hxx + // /usr/include/string.h + // /usr/include/stdlib.h + // /tmp/foo/bar.hxx + // ^@ + // + // A race is possible between updating the database and the target. For + // example, we may detect a line mismatch that renders the target out-of- + // date (say, compile options in the above example). We update the database + // but before getting a chance to update the target, we get interrupted. On + // a subsequent re-run, because the database has been updated, we will miss + // the "target requires update" condition. + // + // If we assume that an update of the database also means an update of the + // target, then this "interrupted update" situation can be easily detected + // by comparing the database and target modification timestamps. + // + class depdb + { + public: + // Open the database for reading. Note that if the file does not exist, + // has wrong format version, or is corrupt, then the database will be + // immediately switched to writing. + // + depdb (const path&); + + // Return the modification time of the database. This value only makes + // sense while reading (in the write mode it will be timestamp_unknown). + // + timestamp + mtime () const {return mtime_;} + + // Update the database modification time in close() even if otherwise + // no modifications are necessary (i.e., the database is in the read + // mode and is at eof). + // + void + touch () {touch_ = true;} + + // Close the database. If this function is not called, then the database + // may be left in the old/currupt state. Note that in the read mode this + // function will "chop off" lines that haven't been read. + // + void + close (); + + // Read the next line. If the result is not NULL, then it is a pointer to + // the next line in the database (which you are free to move from). If you + // then call write(), this line will be overwritten. + // + // If the result is NULL, then it means no next line is available. This + // can be due to several reasons: + // + // - eof reached (you can detect this by calling more() before read()) + // - database is already in the write mode + // - the next line (and the rest of the database are corrupt) + // + string* + read () {return state_ == state::write ? nullptr : read_ ();} + + // Return true if the database is in the read mode and there is at least + // one more line available. Note that there is no guarantee that the line + // is not corrupt. In other words, read() can still return NULL, it just + // won't be because of eof. + // + bool + more () {return state_ == state::read;} + + bool + reading () {return state_ != state::write;} + + bool + writing () {return state_ == state::write;} + + // Write the next line. Note that this switches the database into the + // write mode and no further reading will be possible. + // + void + write (const string& l) {write (l.c_str (), l.size ());} + + void + write (const path& p) {write (p.string ());} + + void + write (const char* s) {write (s, std::strlen (s));} + + void + write (const char*, size_t); + + void + write (char); + + // Read the next line and compare it to the expected value. If it matches, + // return NULL. Otherwise, overwrite it and return the old value (which + // could also be NULL). This strange-sounding result semantics is used to + // detect the "there is a value but it does not match" case for tracing: + // + // if (string* o = d.expect (...)) + // l4 ([&]{trace << "X mismatch forcing update of " << t;}); + // + string* + expect (const string& v) + { + string* l (read ()); + if (l == nullptr || *l != v) + { + write (v); + return l; + } + + return nullptr; + } + + string* + expect (const path& v) + { + string* l (read ()); + if (l == nullptr || path::traits::compare (*l, v.string ()) != 0) + { + write (v); + return l; + } + + return nullptr; + } + + string* + expect (const char* v) + { + string* l (read ()); + if (l == nullptr || *l != v) + { + write (v); + return l; + } + + return nullptr; + } + + private: + void + change (bool flush = true); + + string* + read_ (); + + private: + timestamp mtime_; + std::fstream fs_; + + std::fstream::pos_type pos_; // Start of the last returned line. + string line_; + + enum class state {read, read_eof, write} state_; + bool touch_; + }; +} + +#endif // BUILD2_DEPDB_HXX diff --git a/build2/diagnostics b/build2/diagnostics deleted file mode 100644 index 2c2b4bb..0000000 --- a/build2/diagnostics +++ /dev/null @@ -1,351 +0,0 @@ -// file : build2/diagnostics -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DIAGNOSTICS -#define BUILD2_DIAGNOSTICS - -#include - -#include -#include - -namespace build2 -{ - using butl::diag_record; - - // Throw this exception to terminate the build. The handler should - // assume that the diagnostics has already been issued. - // - class failed: public std::exception {}; - - // Print process commmand line. If the number of elements is specified - // (or the second version is used), then it will print the piped multi- - // process command line, if present. In this case, the expected format - // is as follows: - // - // name1 arg arg ... nullptr - // name2 arg arg ... nullptr - // ... - // nameN arg arg ... nullptr nullptr - // - void - print_process (diag_record&, const char* const* args, size_t n = 0); - - void - print_process (const char* const* args, size_t n = 0); - - inline void - print_process (diag_record& dr, const cstrings& args) - { - print_process (dr, args.data (), args.size ()); - } - - inline void - print_process (const cstrings& args) - { - print_process (args.data (), args.size ()); - } - - // Program verbosity level (-v/--verbose). - // - // 0 - disabled - // 1 - high-level information messages - // 2 - essential underlying commands that are being executed - // 3 - all underlying commands that are being executed - // 4 - information helpful to the user (e.g., why a rule did not match) - // 5 - information helpful to the developer - // 6 - even more detailed information - // - // While uint8 is more than enough, use uint16 for the ease of printing. - // - extern uint16_t verb; - - template inline void l1 (const F& f) {if (verb >= 1) f ();} - template inline void l2 (const F& f) {if (verb >= 2) f ();} - template inline void l3 (const F& f) {if (verb >= 3) f ();} - template inline void l4 (const F& f) {if (verb >= 4) f ();} - template inline void l5 (const F& f) {if (verb >= 5) f ();} - template inline void l6 (const F& f) {if (verb >= 6) f ();} - - // Stream verbosity level. It is determined by the diagnostic type (e.g., - // trace always has maximum verbosity) as well as the program verbosity. It - // is used to decide whether to print relative/absolute paths, and default - // target extensions. - // - // 0 - minimum - // 1 - intermediate - // 2 - maximum - // - // Currently we have the following program to stream verbosity mapping: - // - // fail/error/warn/info <2:0 2:1 >2:2 - // trace *:2 - // - // A stream that hasn't been (yet) assigned any verbosity explicitly (e.g., - // ostringstream) defaults to maximum. - // - const uint16_t stream_verb_min = 0; - const uint16_t stream_verb_max = 2; - - // Default program to stream verbosity mapping, as outlined above. - // - inline uint16_t - stream_verb_map () {return verb < 2 ? 0 : (verb > 2 ? 2 : 1);} - - extern const int stream_verb_index; - - inline uint16_t - stream_verb (ostream& os) - { - uint16_t v (static_cast (os.iword (stream_verb_index))); - return v == 0 ? stream_verb_max : v - 1; - } - - inline void - stream_verb (ostream& os, uint16_t v) - { - os.iword (stream_verb_index) = static_cast (v + 1); - } - - // Diagnostic facility, base infrastructure. - // - using butl::diag_lock; - using butl::diag_stream; - using butl::diag_epilogue; - - // Diagnostics stack. Each frame is "applied" to the fail/error/warn/info - // diag record. - // - // Unfortunately most of our use-cases don't fit into the 2-pointer small - // object optimization of std::function. So we have to complicate things - // a bit here. - // - struct diag_frame - { - explicit - diag_frame (void (*f) (const diag_frame&, const diag_record&)) - : func_ (f), prev_ (stack) {stack = this;} - - // Start with an existing stack, for example, from another thread. - // - explicit - diag_frame (const diag_frame* prev) - : prev_ (stack) {stack = prev;} // Just a restore guard. - - static void - apply (const diag_record& r) - { - for (const diag_frame* f (stack); f != nullptr; f = f->prev_) - f->func_ (*f, r); - } - - ~diag_frame () {stack = prev_;} - - static -#ifdef __cpp_thread_local - thread_local -#else - __thread -#endif - const diag_frame* stack; // Tip of the stack. - - private: - void (*func_) (const diag_frame&, const diag_record&); - const diag_frame* prev_; - }; - - template - struct diag_frame_impl: diag_frame - { - explicit - diag_frame_impl (F f): diag_frame (&thunk), func_ (move (f)) {} - - private: - static void - thunk (const diag_frame& f, const diag_record& r) - { - static_cast (f).func_ (r); - } - - const F func_; - }; - - template - inline diag_frame_impl - make_diag_frame (F f) - { - return diag_frame_impl (move (f)); - } - - // Diagnostic facility, project specifics. - // - struct simple_prologue_base - { - explicit - simple_prologue_base (const char* type, - const char* mod, - const char* name, - uint16_t sverb) - : type_ (type), mod_ (mod), name_ (name), sverb_ (sverb) {} - - void - operator() (const diag_record& r) const; - - private: - const char* type_; - const char* mod_; - const char* name_; - const uint16_t sverb_; - }; - - class location - { - public: - // Note that location maintains a shallow reference to path. Zero lines - // or columns are not printed. - // - location (): file (nullptr), line (0), column (0) {} - location (const path* f, uint64_t l = 0, uint64_t c = 0) - : file (f), line (l), column (c) {} - - bool - empty () const {return file == nullptr;} - - const path* file; - uint64_t line; - uint64_t column; - }; - - struct location_prologue_base - { - location_prologue_base (const char* type, - const char* mod, - const char* name, - const location& l, - uint16_t sverb) - : type_ (type), mod_ (mod), name_ (name), loc_ (l), sverb_ (sverb) {} - - void - operator() (const diag_record& r) const; - - private: - const char* type_; - const char* mod_; - const char* name_; - const location loc_; - const uint16_t sverb_; - }; - - struct basic_mark_base - { - using simple_prologue = butl::diag_prologue; - using location_prologue = butl::diag_prologue; - - explicit - basic_mark_base (const char* type, - diag_epilogue* epilogue = &diag_frame::apply, - uint16_t (*sverb) () = &stream_verb_map, - const char* mod = nullptr, - const char* name = nullptr, - const void* data = nullptr) - : sverb_ (sverb), - type_ (type), mod_ (mod), name_ (name), data_ (data), - epilogue_ (epilogue) {} - - simple_prologue - operator() () const - { - return simple_prologue (epilogue_, type_, mod_, name_, sverb_ ()); - } - - location_prologue - operator() (const location& l) const - { - return location_prologue (epilogue_, type_, mod_, name_, l, sverb_ ()); - } - - template - location_prologue - operator() (const L& l) const - { - return location_prologue ( - epilogue_, type_, mod_, name_, get_location (l, data_), sverb_ ()); - } - - protected: - uint16_t (*sverb_) (); - const char* type_; - const char* mod_; - const char* name_; - const void* data_; - diag_epilogue* const epilogue_; - }; - using basic_mark = butl::diag_mark; - - extern const basic_mark error; - extern const basic_mark warn; - extern const basic_mark info; - extern const basic_mark text; - - // trace - // - struct trace_mark_base: basic_mark_base - { - explicit - trace_mark_base (const char* name, const void* data = nullptr) - : trace_mark_base (nullptr, name, data) {} - - trace_mark_base (const char* mod, - const char* name, - const void* data = nullptr) - : basic_mark_base ("trace", - nullptr, // No diag stack. - []() {return stream_verb_max;}, - mod, - name, - data) {} - }; - using trace_mark = butl::diag_mark; - using tracer = trace_mark; - - // fail - // - struct fail_mark_base: basic_mark_base - { - explicit - fail_mark_base (const char* type, - const void* data = nullptr) - : basic_mark_base (type, - [](const diag_record& r) - { - diag_frame::apply (r); - r.flush (); - throw failed (); - }, - &stream_verb_map, - nullptr, - nullptr, - data) {} - }; - using fail_mark = butl::diag_mark; - - struct fail_end_base - { - [[noreturn]] void - operator() (const diag_record& r) const - { - // If we just throw then the record's destructor will see an active - // exception and will not flush the record. - // - r.flush (); - throw failed (); - } - }; - using fail_end = butl::diag_noreturn_end; - - extern const fail_mark fail; - extern const fail_end endf; -} - -#endif // BUILD2_DIAGNOSTICS diff --git a/build2/diagnostics.cxx b/build2/diagnostics.cxx index 0c4e63f..25339f7 100644 --- a/build2/diagnostics.cxx +++ b/build2/diagnostics.cxx @@ -2,11 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strchr() -#include +#include using namespace std; diff --git a/build2/diagnostics.hxx b/build2/diagnostics.hxx new file mode 100644 index 0000000..692115b --- /dev/null +++ b/build2/diagnostics.hxx @@ -0,0 +1,351 @@ +// file : build2/diagnostics.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIAGNOSTICS_HXX +#define BUILD2_DIAGNOSTICS_HXX + +#include + +#include +#include + +namespace build2 +{ + using butl::diag_record; + + // Throw this exception to terminate the build. The handler should + // assume that the diagnostics has already been issued. + // + class failed: public std::exception {}; + + // Print process commmand line. If the number of elements is specified + // (or the second version is used), then it will print the piped multi- + // process command line, if present. In this case, the expected format + // is as follows: + // + // name1 arg arg ... nullptr + // name2 arg arg ... nullptr + // ... + // nameN arg arg ... nullptr nullptr + // + void + print_process (diag_record&, const char* const* args, size_t n = 0); + + void + print_process (const char* const* args, size_t n = 0); + + inline void + print_process (diag_record& dr, const cstrings& args) + { + print_process (dr, args.data (), args.size ()); + } + + inline void + print_process (const cstrings& args) + { + print_process (args.data (), args.size ()); + } + + // Program verbosity level (-v/--verbose). + // + // 0 - disabled + // 1 - high-level information messages + // 2 - essential underlying commands that are being executed + // 3 - all underlying commands that are being executed + // 4 - information helpful to the user (e.g., why a rule did not match) + // 5 - information helpful to the developer + // 6 - even more detailed information + // + // While uint8 is more than enough, use uint16 for the ease of printing. + // + extern uint16_t verb; + + template inline void l1 (const F& f) {if (verb >= 1) f ();} + template inline void l2 (const F& f) {if (verb >= 2) f ();} + template inline void l3 (const F& f) {if (verb >= 3) f ();} + template inline void l4 (const F& f) {if (verb >= 4) f ();} + template inline void l5 (const F& f) {if (verb >= 5) f ();} + template inline void l6 (const F& f) {if (verb >= 6) f ();} + + // Stream verbosity level. It is determined by the diagnostic type (e.g., + // trace always has maximum verbosity) as well as the program verbosity. It + // is used to decide whether to print relative/absolute paths, and default + // target extensions. + // + // 0 - minimum + // 1 - intermediate + // 2 - maximum + // + // Currently we have the following program to stream verbosity mapping: + // + // fail/error/warn/info <2:0 2:1 >2:2 + // trace *:2 + // + // A stream that hasn't been (yet) assigned any verbosity explicitly (e.g., + // ostringstream) defaults to maximum. + // + const uint16_t stream_verb_min = 0; + const uint16_t stream_verb_max = 2; + + // Default program to stream verbosity mapping, as outlined above. + // + inline uint16_t + stream_verb_map () {return verb < 2 ? 0 : (verb > 2 ? 2 : 1);} + + extern const int stream_verb_index; + + inline uint16_t + stream_verb (ostream& os) + { + uint16_t v (static_cast (os.iword (stream_verb_index))); + return v == 0 ? stream_verb_max : v - 1; + } + + inline void + stream_verb (ostream& os, uint16_t v) + { + os.iword (stream_verb_index) = static_cast (v + 1); + } + + // Diagnostic facility, base infrastructure. + // + using butl::diag_lock; + using butl::diag_stream; + using butl::diag_epilogue; + + // Diagnostics stack. Each frame is "applied" to the fail/error/warn/info + // diag record. + // + // Unfortunately most of our use-cases don't fit into the 2-pointer small + // object optimization of std::function. So we have to complicate things + // a bit here. + // + struct diag_frame + { + explicit + diag_frame (void (*f) (const diag_frame&, const diag_record&)) + : func_ (f), prev_ (stack) {stack = this;} + + // Start with an existing stack, for example, from another thread. + // + explicit + diag_frame (const diag_frame* prev) + : prev_ (stack) {stack = prev;} // Just a restore guard. + + static void + apply (const diag_record& r) + { + for (const diag_frame* f (stack); f != nullptr; f = f->prev_) + f->func_ (*f, r); + } + + ~diag_frame () {stack = prev_;} + + static +#ifdef __cpp_thread_local + thread_local +#else + __thread +#endif + const diag_frame* stack; // Tip of the stack. + + private: + void (*func_) (const diag_frame&, const diag_record&); + const diag_frame* prev_; + }; + + template + struct diag_frame_impl: diag_frame + { + explicit + diag_frame_impl (F f): diag_frame (&thunk), func_ (move (f)) {} + + private: + static void + thunk (const diag_frame& f, const diag_record& r) + { + static_cast (f).func_ (r); + } + + const F func_; + }; + + template + inline diag_frame_impl + make_diag_frame (F f) + { + return diag_frame_impl (move (f)); + } + + // Diagnostic facility, project specifics. + // + struct simple_prologue_base + { + explicit + simple_prologue_base (const char* type, + const char* mod, + const char* name, + uint16_t sverb) + : type_ (type), mod_ (mod), name_ (name), sverb_ (sverb) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* mod_; + const char* name_; + const uint16_t sverb_; + }; + + class location + { + public: + // Note that location maintains a shallow reference to path. Zero lines + // or columns are not printed. + // + location (): file (nullptr), line (0), column (0) {} + location (const path* f, uint64_t l = 0, uint64_t c = 0) + : file (f), line (l), column (c) {} + + bool + empty () const {return file == nullptr;} + + const path* file; + uint64_t line; + uint64_t column; + }; + + struct location_prologue_base + { + location_prologue_base (const char* type, + const char* mod, + const char* name, + const location& l, + uint16_t sverb) + : type_ (type), mod_ (mod), name_ (name), loc_ (l), sverb_ (sverb) {} + + void + operator() (const diag_record& r) const; + + private: + const char* type_; + const char* mod_; + const char* name_; + const location loc_; + const uint16_t sverb_; + }; + + struct basic_mark_base + { + using simple_prologue = butl::diag_prologue; + using location_prologue = butl::diag_prologue; + + explicit + basic_mark_base (const char* type, + diag_epilogue* epilogue = &diag_frame::apply, + uint16_t (*sverb) () = &stream_verb_map, + const char* mod = nullptr, + const char* name = nullptr, + const void* data = nullptr) + : sverb_ (sverb), + type_ (type), mod_ (mod), name_ (name), data_ (data), + epilogue_ (epilogue) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, type_, mod_, name_, sverb_ ()); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, type_, mod_, name_, l, sverb_ ()); + } + + template + location_prologue + operator() (const L& l) const + { + return location_prologue ( + epilogue_, type_, mod_, name_, get_location (l, data_), sverb_ ()); + } + + protected: + uint16_t (*sverb_) (); + const char* type_; + const char* mod_; + const char* name_; + const void* data_; + diag_epilogue* const epilogue_; + }; + using basic_mark = butl::diag_mark; + + extern const basic_mark error; + extern const basic_mark warn; + extern const basic_mark info; + extern const basic_mark text; + + // trace + // + struct trace_mark_base: basic_mark_base + { + explicit + trace_mark_base (const char* name, const void* data = nullptr) + : trace_mark_base (nullptr, name, data) {} + + trace_mark_base (const char* mod, + const char* name, + const void* data = nullptr) + : basic_mark_base ("trace", + nullptr, // No diag stack. + []() {return stream_verb_max;}, + mod, + name, + data) {} + }; + using trace_mark = butl::diag_mark; + using tracer = trace_mark; + + // fail + // + struct fail_mark_base: basic_mark_base + { + explicit + fail_mark_base (const char* type, + const void* data = nullptr) + : basic_mark_base (type, + [](const diag_record& r) + { + diag_frame::apply (r); + r.flush (); + throw failed (); + }, + &stream_verb_map, + nullptr, + nullptr, + data) {} + }; + using fail_mark = butl::diag_mark; + + struct fail_end_base + { + [[noreturn]] void + operator() (const diag_record& r) const + { + // If we just throw then the record's destructor will see an active + // exception and will not flush the record. + // + r.flush (); + throw failed (); + } + }; + using fail_end = butl::diag_noreturn_end; + + extern const fail_mark fail; + extern const fail_end endf; +} + +#endif // BUILD2_DIAGNOSTICS_HXX diff --git a/build2/dist/init b/build2/dist/init deleted file mode 100644 index 67ec0a4..0000000 --- a/build2/dist/init +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/dist/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DIST_INIT -#define BUILD2_DIST_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace dist - { - void - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_DIST_INIT diff --git a/build2/dist/init.cxx b/build2/dist/init.cxx index 41927cd..d002868 100644 --- a/build2/dist/init.cxx +++ b/build2/dist/init.cxx @@ -2,17 +2,17 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/dist/init.hxx b/build2/dist/init.hxx new file mode 100644 index 0000000..45d47df --- /dev/null +++ b/build2/dist/init.hxx @@ -0,0 +1,31 @@ +// file : build2/dist/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_INIT_HXX +#define BUILD2_DIST_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace dist + { + void + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_DIST_INIT_HXX diff --git a/build2/dist/module b/build2/dist/module deleted file mode 100644 index 5510423..0000000 --- a/build2/dist/module +++ /dev/null @@ -1,65 +0,0 @@ -// file : build2/dist/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DIST_MODULE -#define BUILD2_DIST_MODULE - -#include -#include - -#include -#include - -namespace build2 -{ - namespace dist - { - struct module: module_base - { - static const string name; - - const variable& var_dist_package; - - // Distribution post-processing callbacks. - // - // The last component in the pattern may contain shell wildcards. If the - // path contains a directory, then it is matched from the distribution - // root only. Otherwise, it is matched against all the files being - // distributed. For example: - // - // buildfile - every buildfile - // ./buildfile - root buildfile only - // tests/buildfile - tests/buildfile only - // - // The callback is called with the absolute path of the matching file - // after it has been copied to the distribution directory. The project's - // root scope and callback-specific data are passed along. - // - using callback_func = void (const path&, const scope&, void*); - - void - register_callback (path pattern, callback_func* f, void* data) - { - callbacks_.push_back (callback {move (pattern), f, data}); - } - - // Implementation details. - // - module (const variable& v_d_p) - : var_dist_package (v_d_p) {} - - public: - struct callback - { - const path pattern; - callback_func* function; - void* data; - }; - - vector callbacks_; - }; - } -} - -#endif // BUILD2_DIST_MODULE diff --git a/build2/dist/module.cxx b/build2/dist/module.cxx index 05ca1cb..4f14ddb 100644 --- a/build2/dist/module.cxx +++ b/build2/dist/module.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/dist/module.hxx b/build2/dist/module.hxx new file mode 100644 index 0000000..e6eb975 --- /dev/null +++ b/build2/dist/module.hxx @@ -0,0 +1,65 @@ +// file : build2/dist/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_MODULE_HXX +#define BUILD2_DIST_MODULE_HXX + +#include +#include + +#include +#include + +namespace build2 +{ + namespace dist + { + struct module: module_base + { + static const string name; + + const variable& var_dist_package; + + // Distribution post-processing callbacks. + // + // The last component in the pattern may contain shell wildcards. If the + // path contains a directory, then it is matched from the distribution + // root only. Otherwise, it is matched against all the files being + // distributed. For example: + // + // buildfile - every buildfile + // ./buildfile - root buildfile only + // tests/buildfile - tests/buildfile only + // + // The callback is called with the absolute path of the matching file + // after it has been copied to the distribution directory. The project's + // root scope and callback-specific data are passed along. + // + using callback_func = void (const path&, const scope&, void*); + + void + register_callback (path pattern, callback_func* f, void* data) + { + callbacks_.push_back (callback {move (pattern), f, data}); + } + + // Implementation details. + // + module (const variable& v_d_p) + : var_dist_package (v_d_p) {} + + public: + struct callback + { + const path pattern; + callback_func* function; + void* data; + }; + + vector callbacks_; + }; + } +} + +#endif // BUILD2_DIST_MODULE_HXX diff --git a/build2/dist/operation b/build2/dist/operation deleted file mode 100644 index 845130f..0000000 --- a/build2/dist/operation +++ /dev/null @@ -1,21 +0,0 @@ -// file : build2/dist/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DIST_OPERATION -#define BUILD2_DIST_OPERATION - -#include -#include - -#include - -namespace build2 -{ - namespace dist - { - extern const meta_operation_info dist; - } -} - -#endif // BUILD2_DIST_OPERATION diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx index 859225d..d4ca3c6 100644 --- a/build2/dist/operation.cxx +++ b/build2/dist/operation.cxx @@ -2,20 +2,20 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // path_match() +#include // path_match() -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/dist/operation.hxx b/build2/dist/operation.hxx new file mode 100644 index 0000000..c72e467 --- /dev/null +++ b/build2/dist/operation.hxx @@ -0,0 +1,21 @@ +// file : build2/dist/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_OPERATION_HXX +#define BUILD2_DIST_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace dist + { + extern const meta_operation_info dist; + } +} + +#endif // BUILD2_DIST_OPERATION_HXX diff --git a/build2/dist/rule b/build2/dist/rule deleted file mode 100644 index 0a5cc3a..0000000 --- a/build2/dist/rule +++ /dev/null @@ -1,40 +0,0 @@ -// file : build2/dist/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DIST_RULE -#define BUILD2_DIST_RULE - -#include -#include - -#include -#include -#include - -namespace build2 -{ - namespace dist - { - // This is the default rule that simply matches all the prerequisites. - // - // A custom rule (usually the same as perform_update) may be necessary to - // enter ad hoc prerequisites (like generated test input/output) or - // establishing group links (so that we see the dist variable set on a - // group). - // - class rule: public build2::rule - { - public: - rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - }; - } -} - -#endif // BUILD2_DIST_RULE diff --git a/build2/dist/rule.cxx b/build2/dist/rule.cxx index 338f7f9..ab00d41 100644 --- a/build2/dist/rule.cxx +++ b/build2/dist/rule.cxx @@ -2,12 +2,12 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include using namespace std; diff --git a/build2/dist/rule.hxx b/build2/dist/rule.hxx new file mode 100644 index 0000000..0524029 --- /dev/null +++ b/build2/dist/rule.hxx @@ -0,0 +1,40 @@ +// file : build2/dist/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DIST_RULE_HXX +#define BUILD2_DIST_RULE_HXX + +#include +#include + +#include +#include +#include + +namespace build2 +{ + namespace dist + { + // This is the default rule that simply matches all the prerequisites. + // + // A custom rule (usually the same as perform_update) may be necessary to + // enter ad hoc prerequisites (like generated test input/output) or + // establishing group links (so that we see the dist variable set on a + // group). + // + class rule: public build2::rule + { + public: + rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + }; + } +} + +#endif // BUILD2_DIST_RULE_HXX diff --git a/build2/dump b/build2/dump deleted file mode 100644 index a1d9b07..0000000 --- a/build2/dump +++ /dev/null @@ -1,21 +0,0 @@ -// file : build2/dump -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_DUMP -#define BUILD2_DUMP - -#include -#include - -#include - -namespace build2 -{ - // Dump the state pertaining to the current action. - // - void - dump (); -} - -#endif // BUILD2_DUMP diff --git a/build2/dump.cxx b/build2/dump.cxx index 6d055ae..ab2e73f 100644 --- a/build2/dump.cxx +++ b/build2/dump.cxx @@ -2,13 +2,13 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include using namespace std; diff --git a/build2/dump.hxx b/build2/dump.hxx new file mode 100644 index 0000000..e456540 --- /dev/null +++ b/build2/dump.hxx @@ -0,0 +1,21 @@ +// file : build2/dump.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_DUMP_HXX +#define BUILD2_DUMP_HXX + +#include +#include + +#include + +namespace build2 +{ + // Dump the state pertaining to the current action. + // + void + dump (); +} + +#endif // BUILD2_DUMP_HXX diff --git a/build2/file b/build2/file deleted file mode 100644 index 2200c26..0000000 --- a/build2/file +++ /dev/null @@ -1,187 +0,0 @@ -// file : build2/file -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_FILE -#define BUILD2_FILE - -#include - -#include -#include - -#include -#include // list_value - -namespace build2 -{ - class target; - class location; - class prerequisite_key; - - using subprojects = std::map; - - extern const dir_path build_dir; // build - extern const dir_path bootstrap_dir; // build/bootstrap - - extern const path root_file; // build/root.build - extern const path bootstrap_file; // build/bootstrap.build - extern const path src_root_file; // build/bootstrap/src-root.build - extern const path export_file; // build/export.build - extern const path config_file; // build/config.build - - bool - is_src_root (const dir_path&); - - bool - is_out_root (const dir_path&); - - // Given an src_base directory, look for a project's src_root based on the - // presence of known special files. Return empty path if not found. Note - // that if the input is normalized/actualized, then the output will be as - // well. - // - dir_path - find_src_root (const dir_path&); - - // The same as above but for project's out. Note that we also check whether - // a directory happens to be src_root, in case this is an in-tree build. The - // second argument is the out flag that is set to true if this is src_root. - // Note that if the input is normalized/actualized, then the output will be - // as well. - // - dir_path - find_out_root (const dir_path&, bool* src = nullptr); - - // If buildfile is '-', then read from STDIN. - // - void - source (scope& root, scope& base, const path&); - - // As above but first check if this buildfile has already been sourced for - // the base scope. Return false if the file has already been sourced. - // - bool - source_once (scope& root, scope& base, const path&); - - // As above but checks against the specified scope rather than base. - // - bool - source_once (scope& root, scope& base, const path&, scope& once); - - // Create project's root scope. Only set the src_root variable if the - // passed src_root value is not empty. The scope argument is only used - // as proof of lock. - // - scope& - create_root (scope&, const dir_path& out_root, const dir_path& src_root); - - // Setup root scope. Note that it assume the src_root variable - // has already been set. - // - void - setup_root (scope&); - - // Setup the base scope (set *_base variables, etc). - // - scope& - setup_base (scope_map::iterator, - const dir_path& out_base, - const dir_path& src_base); - - // Return a scope for the specified directory (first). Note that switching - // to this scope might also involve switch to a new root scope (second) if - // the new scope is in another project. In the new scope is not in any - // project, then NULL is returned in second. - // - pair - switch_scope (scope& root, const dir_path&); - - // Bootstrap the project's root scope, the out part. - // - void - bootstrap_out (scope& root); - - // Bootstrap the project's root scope, the src part. Return true if - // we loaded anything (which confirms the src_root is not bogus). - // - bool - bootstrap_src (scope& root); - - // Return true if this scope has already been bootstrapped, that is, the - // following calls have already been made: - // - // bootstrap_out() - // setup_root() - // bootstrap_src() - // - bool - bootstrapped (scope& root); - - // Create and bootstrap outer root scopes, if any. Loading is - // done by load_root_pre() below. - // - void - create_bootstrap_outer (scope& root); - - // Create and bootstrap inner root scopes between root and base, - // if any. Return the innermost created root scope or root if - // none were created. Loading is done by load_root_pre() below. - // - scope& - create_bootstrap_inner (scope& root, const dir_path& out_base); - - // Load project's root[-pre].build unless already loaded. Also - // make sure all outer root scopes are loaded prior to loading - // this root scope. - // - void - load_root_pre (scope& root); - - // Extract the specified variable value from a buildfile. It is expected to - // be the first non-comment line and not to rely on any variable expansion - // other than those from the global scope or any variable overrides. Return - // an indication of whether the variable was found. The scope is only used - // as proof of lock (though we don't modify anything). - // - pair - extract_variable (scope&, const path&, const variable&); - - // Import has two phases: the first is triggered by the import - // directive in the buildfile. It will try to find and load the - // project. Failed that, it will return the project-qualified - // name of the target which will be used to create a project- - // qualified prerequisite. This gives the rule that will be - // searching this prerequisite a chance to do some target-type - // specific search. For example, a C++ link rule can search - // for lib{} prerequisites in the C++ compiler default library - // search paths (so that we end up with functionality identical - // to -lfoo). If, however, the rule didn't do any of that (or - // failed to find anything usable), it calls the standard - // prerequisite search() function which sees this is a project- - // qualified prerequisite and goes straight to the second phase - // of import. Here, currently, we simply fail but in the future - // this will be the place where we can call custom "last resort" - // import hooks. For example, we can hook a package manager that - // will say, "Hey, I see you are trying to import foo and I see - // there is a package foo available in repository bar. Wanna - // download and use it?" - // - names - import (scope& base, name, const location&); - - const target& - import (const prerequisite_key&); - - // As above but only imports as an already existing target. Unlike the above - // version, this one can be called during the execute phase. - // - // Note: similar to search_existing(). - // - const target* - import_existing (const prerequisite_key&); -} - -#include - -#endif // BUILD2_FILE diff --git a/build2/file.cxx b/build2/file.cxx index 3d7f1b8..ddcce82 100644 --- a/build2/file.cxx +++ b/build2/file.cxx @@ -2,22 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // cin -#include -#include -#include -#include // exists() -#include -#include +#include +#include +#include +#include // exists() +#include +#include -#include -#include -#include +#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/file.hxx b/build2/file.hxx new file mode 100644 index 0000000..7bfae14 --- /dev/null +++ b/build2/file.hxx @@ -0,0 +1,187 @@ +// file : build2/file.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_FILE_HXX +#define BUILD2_FILE_HXX + +#include + +#include +#include + +#include +#include // list_value + +namespace build2 +{ + class target; + class location; + class prerequisite_key; + + using subprojects = std::map; + + extern const dir_path build_dir; // build + extern const dir_path bootstrap_dir; // build/bootstrap + + extern const path root_file; // build/root.build + extern const path bootstrap_file; // build/bootstrap.build + extern const path src_root_file; // build/bootstrap/src-root.build + extern const path export_file; // build/export.build + extern const path config_file; // build/config.build + + bool + is_src_root (const dir_path&); + + bool + is_out_root (const dir_path&); + + // Given an src_base directory, look for a project's src_root based on the + // presence of known special files. Return empty path if not found. Note + // that if the input is normalized/actualized, then the output will be as + // well. + // + dir_path + find_src_root (const dir_path&); + + // The same as above but for project's out. Note that we also check whether + // a directory happens to be src_root, in case this is an in-tree build. The + // second argument is the out flag that is set to true if this is src_root. + // Note that if the input is normalized/actualized, then the output will be + // as well. + // + dir_path + find_out_root (const dir_path&, bool* src = nullptr); + + // If buildfile is '-', then read from STDIN. + // + void + source (scope& root, scope& base, const path&); + + // As above but first check if this buildfile has already been sourced for + // the base scope. Return false if the file has already been sourced. + // + bool + source_once (scope& root, scope& base, const path&); + + // As above but checks against the specified scope rather than base. + // + bool + source_once (scope& root, scope& base, const path&, scope& once); + + // Create project's root scope. Only set the src_root variable if the + // passed src_root value is not empty. The scope argument is only used + // as proof of lock. + // + scope& + create_root (scope&, const dir_path& out_root, const dir_path& src_root); + + // Setup root scope. Note that it assume the src_root variable + // has already been set. + // + void + setup_root (scope&); + + // Setup the base scope (set *_base variables, etc). + // + scope& + setup_base (scope_map::iterator, + const dir_path& out_base, + const dir_path& src_base); + + // Return a scope for the specified directory (first). Note that switching + // to this scope might also involve switch to a new root scope (second) if + // the new scope is in another project. In the new scope is not in any + // project, then NULL is returned in second. + // + pair + switch_scope (scope& root, const dir_path&); + + // Bootstrap the project's root scope, the out part. + // + void + bootstrap_out (scope& root); + + // Bootstrap the project's root scope, the src part. Return true if + // we loaded anything (which confirms the src_root is not bogus). + // + bool + bootstrap_src (scope& root); + + // Return true if this scope has already been bootstrapped, that is, the + // following calls have already been made: + // + // bootstrap_out() + // setup_root() + // bootstrap_src() + // + bool + bootstrapped (scope& root); + + // Create and bootstrap outer root scopes, if any. Loading is + // done by load_root_pre() below. + // + void + create_bootstrap_outer (scope& root); + + // Create and bootstrap inner root scopes between root and base, + // if any. Return the innermost created root scope or root if + // none were created. Loading is done by load_root_pre() below. + // + scope& + create_bootstrap_inner (scope& root, const dir_path& out_base); + + // Load project's root[-pre].build unless already loaded. Also + // make sure all outer root scopes are loaded prior to loading + // this root scope. + // + void + load_root_pre (scope& root); + + // Extract the specified variable value from a buildfile. It is expected to + // be the first non-comment line and not to rely on any variable expansion + // other than those from the global scope or any variable overrides. Return + // an indication of whether the variable was found. The scope is only used + // as proof of lock (though we don't modify anything). + // + pair + extract_variable (scope&, const path&, const variable&); + + // Import has two phases: the first is triggered by the import + // directive in the buildfile. It will try to find and load the + // project. Failed that, it will return the project-qualified + // name of the target which will be used to create a project- + // qualified prerequisite. This gives the rule that will be + // searching this prerequisite a chance to do some target-type + // specific search. For example, a C++ link rule can search + // for lib{} prerequisites in the C++ compiler default library + // search paths (so that we end up with functionality identical + // to -lfoo). If, however, the rule didn't do any of that (or + // failed to find anything usable), it calls the standard + // prerequisite search() function which sees this is a project- + // qualified prerequisite and goes straight to the second phase + // of import. Here, currently, we simply fail but in the future + // this will be the place where we can call custom "last resort" + // import hooks. For example, we can hook a package manager that + // will say, "Hey, I see you are trying to import foo and I see + // there is a package foo available in repository bar. Wanna + // download and use it?" + // + names + import (scope& base, name, const location&); + + const target& + import (const prerequisite_key&); + + // As above but only imports as an already existing target. Unlike the above + // version, this one can be called during the execute phase. + // + // Note: similar to search_existing(). + // + const target* + import_existing (const prerequisite_key&); +} + +#include + +#endif // BUILD2_FILE_HXX diff --git a/build2/filesystem b/build2/filesystem deleted file mode 100644 index bf0302e..0000000 --- a/build2/filesystem +++ /dev/null @@ -1,107 +0,0 @@ -// file : build2/filesystem -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_FILESYSTEM -#define BUILD2_FILESYSTEM - -#include - -#include -#include - -// Higher-level filesystem utilities built on top of . -// -namespace build2 -{ - // The dual interface wrapper for the {mk,rm}{file,dir}() functions - // below that allows you to use it as a true/false return or a more - // detailed enum from - // - template - struct fs_status - { - T v; - fs_status (T v): v (v) {}; - operator T () const {return v;} - explicit operator bool () const {return v == T::success;} - }; - - // Create the directory and print the standard diagnostics starting from - // the specified verbosity level. - // - // Note that this implementation is not suitable if it is expected that the - // directory will exist in the majority of cases and performance is - // important. See the fsdir{} rule for details. - // - using mkdir_status = butl::mkdir_status; - - fs_status - mkdir (const dir_path&, uint16_t verbosity = 1); - - fs_status - mkdir_p (const dir_path&, uint16_t verbosity = 1); - - // Remove the file and print the standard diagnostics starting from the - // specified verbosity level. The second argument is only used in - // diagnostics, to print the target name. Passing the path for target will - // result in the relative path being printed. - // - using rmfile_status = butl::rmfile_status; - - template - fs_status - rmfile (const path&, const T& target, uint16_t verbosity = 1); - - inline fs_status - rmfile (const path& f, int verbosity = 1) // Literal overload (int). - { - return rmfile (f, f, static_cast (verbosity)); - } - - // Similar to rmfile() but for directories (note: not -r). - // - using rmdir_status = butl::rmdir_status; - - template - fs_status - rmdir (const dir_path&, const T& target, uint16_t verbosity = 1); - - inline fs_status - rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int). - { - return rmdir (d, d, static_cast (verbosity)); - } - - // Remove the directory recursively and print the standard diagnostics - // starting from the specified verbosity level. Note that this function - // returns not_empty if we try to remove a working directory. If the dir - // argument is false, then the directory itself is not removed. - // - // @@ Collides (via ADL) with butl::rmdir_r(), which sucks. - // - fs_status - rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1); - - // Check for a file, directory or filesystem entry existence. Print the - // diagnostics and fail on system error. - // - bool - exists (const path&, bool follow_symlinks = true); - - bool - exists (const dir_path&); - - bool - entry_exists (const path&, bool follow_symlinks = false); - - // Check for a directory emptiness. Print the diagnostics and fail on system - // error. - // - bool - empty (const dir_path&); -} - -#include - -#endif // BUILD2_FILESYSTEM diff --git a/build2/filesystem.cxx b/build2/filesystem.cxx index 86bdff8..9d9b3b6 100644 --- a/build2/filesystem.cxx +++ b/build2/filesystem.cxx @@ -2,9 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/filesystem.hxx b/build2/filesystem.hxx new file mode 100644 index 0000000..f211f35 --- /dev/null +++ b/build2/filesystem.hxx @@ -0,0 +1,107 @@ +// file : build2/filesystem.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_FILESYSTEM_HXX +#define BUILD2_FILESYSTEM_HXX + +#include + +#include +#include + +// Higher-level filesystem utilities built on top of . +// +namespace build2 +{ + // The dual interface wrapper for the {mk,rm}{file,dir}() functions + // below that allows you to use it as a true/false return or a more + // detailed enum from + // + template + struct fs_status + { + T v; + fs_status (T v): v (v) {}; + operator T () const {return v;} + explicit operator bool () const {return v == T::success;} + }; + + // Create the directory and print the standard diagnostics starting from + // the specified verbosity level. + // + // Note that this implementation is not suitable if it is expected that the + // directory will exist in the majority of cases and performance is + // important. See the fsdir{} rule for details. + // + using mkdir_status = butl::mkdir_status; + + fs_status + mkdir (const dir_path&, uint16_t verbosity = 1); + + fs_status + mkdir_p (const dir_path&, uint16_t verbosity = 1); + + // Remove the file and print the standard diagnostics starting from the + // specified verbosity level. The second argument is only used in + // diagnostics, to print the target name. Passing the path for target will + // result in the relative path being printed. + // + using rmfile_status = butl::rmfile_status; + + template + fs_status + rmfile (const path&, const T& target, uint16_t verbosity = 1); + + inline fs_status + rmfile (const path& f, int verbosity = 1) // Literal overload (int). + { + return rmfile (f, f, static_cast (verbosity)); + } + + // Similar to rmfile() but for directories (note: not -r). + // + using rmdir_status = butl::rmdir_status; + + template + fs_status + rmdir (const dir_path&, const T& target, uint16_t verbosity = 1); + + inline fs_status + rmdir (const dir_path& d, int verbosity = 1) // Literal overload (int). + { + return rmdir (d, d, static_cast (verbosity)); + } + + // Remove the directory recursively and print the standard diagnostics + // starting from the specified verbosity level. Note that this function + // returns not_empty if we try to remove a working directory. If the dir + // argument is false, then the directory itself is not removed. + // + // @@ Collides (via ADL) with butl::rmdir_r(), which sucks. + // + fs_status + rmdir_r (const dir_path&, bool dir = true, uint16_t verbosity = 1); + + // Check for a file, directory or filesystem entry existence. Print the + // diagnostics and fail on system error. + // + bool + exists (const path&, bool follow_symlinks = true); + + bool + exists (const dir_path&); + + bool + entry_exists (const path&, bool follow_symlinks = false); + + // Check for a directory emptiness. Print the diagnostics and fail on system + // error. + // + bool + empty (const dir_path&); +} + +#include + +#endif // BUILD2_FILESYSTEM_HXX diff --git a/build2/filesystem.txx b/build2/filesystem.txx index 708c75e..83e3677 100644 --- a/build2/filesystem.txx +++ b/build2/filesystem.txx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // work -#include +#include // work +#include namespace build2 { diff --git a/build2/function b/build2/function deleted file mode 100644 index ef5d6bf..0000000 --- a/build2/function +++ /dev/null @@ -1,699 +0,0 @@ -// file : build2/function -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_FUNCTION -#define BUILD2_FUNCTION - -#include // index_sequence -#include // aligned_storage -#include - -#include -#include - -#include -#include - -namespace build2 -{ - // Functions can be overloaded based on types of their arguments but - // arguments can be untyped and a function can elect to accept an argument - // of any type. - // - // Functions can be qualified (e.g, string.length(), path.directory()) and - // unqualified (e.g., length(), directory()). Only functions overloaded on - // static types can be unqualified plus they should also define a qualified - // alias. - // - // Low-level function implementation would be called with a list of values - // as arguments. There is also higher-level, more convenient support for - // defining functions as pointers to functions (including capture-less - // lambdas), pointers to member functions (e.g., string::size()), or - // pointers to data members (e.g., name::type). In this case the build2 - // function types are automatically matched to C++ function types according - // to these rules: - // - // T - statically-typed (value_traits must be defined) - // names - untyped - // value - any type - // T* - NULL-able argument (here T can be names) - // value* - NULL-able any type (never NULL itself, use value::null) - // optional - optional argument (here T can be T*, names, value) - // - // Optional arguments must be last. In case of a failure the function is - // expected to issue diagnostics and throw failed. Note that the arguments - // are conceptually "moved" and can be reused by the implementation. - // - // Normally functions come in families that share a common qualification - // (e.g., string. or path.). The function_family class is a "registrar" - // that simplifies handling of function families. For example: - // - // function_family f ("string"); - // - // // Register length() and string.length(). - // // - // f["length"] = &string::size; - // - // // Register string.max_size(). - // // - // f[".max_size"] = []() {return string ().max_size ();}; - // - // For more examples/ideas, study the existing function families (reside - // in the functions-*.cxx files). - // - struct function_overload; - - using function_impl = value (vector_view, const function_overload&); - - struct function_overload - { - const char* name; // Set to point to key by insert() below. - const char* alt_name; // Alternative name, NULL if none. This is the - // qualified name for unqualified or vice verse. - - // Arguments. - // - // A function can have a number of optional arguments. Arguments can also - // be typed. A non-existent entry in arg_types means a value of any type. - // A NULL entry means an untyped value. - // - // If arg_max equals to arg_variadic, then the function takes an unlimited - // number of arguments. In this case the semantics of arg_min and - // arg_types is unchanged. - // - static const size_t arg_variadic = size_t (~0); - - using types = vector_view>; - - const size_t arg_min; - const size_t arg_max; - const types arg_types; - - // Function implementation. - // - function_impl* const impl; - - // Auxiliary data storage. Note that it is assumed to be POD (no - // destructors, bitwise copy, etc). - // - std::aligned_storage::type data; - static const size_t data_size = sizeof (decltype (data)); - - function_overload () = default; - - function_overload (const char* an, - size_t mi, size_t ma, types ts, - function_impl* im) - : alt_name (an), - arg_min (mi), arg_max (ma), arg_types (move (ts)), - impl (im) {} - - template - function_overload (const char* an, - size_t mi, size_t ma, types ts, - function_impl* im, - D d) - : function_overload (an, mi, ma, move (ts), im) - { - // std::is_pod appears to be broken in VC15 and also in GCC up to - // 5 (pointers to members). - // -#if !((defined(_MSC_VER) && _MSC_VER <= 1910) || \ - (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5)) - static_assert (std::is_pod::value, "type is not POD"); -#endif - static_assert (sizeof (D) <= data_size, "insufficient space"); - new (&data) D (move (d)); - } - }; - - ostream& - operator<< (ostream&, const function_overload&); // Print signature. - - class function_map - { - public: - using map_type = std::unordered_multimap; - using iterator = map_type::iterator; - using const_iterator = map_type::const_iterator; - - iterator - insert (string name, function_overload); - - void - erase (iterator i) {map_.erase (i);} - - value - call (const string& name, vector_view args, const location& l) const - { - return call (name, args, l, true).first; - } - - // As above but do not fail if no match was found (but still do if the - // match is ambiguous). Instead return an indication of whether the call - // was made. Used to issue custom diagnostics when calling internal - // functions. - // - pair - try_call (const string& name, - vector_view args, - const location& l) const - { - return call (name, args, l, false); - } - - iterator - begin () {return map_.begin ();} - - iterator - end () {return map_.end ();} - - const_iterator - begin () const {return map_.begin ();} - - const_iterator - end () const {return map_.end ();} - - private: - pair - call (const string&, vector_view, const location&, bool fail) const; - - map_type map_; - }; - - extern function_map functions; - - class function_family - { - public: - // The default thunk catches invalid_argument and issues diagnostics - // by assuming it is related to function arguments and contains useful - // description. - // - // In order to implement a custom thunk (e.g., to catch additional extra - // exceptions), you would normally call the default implementation. - // - static value - default_thunk (vector_view, const function_overload&); - - // A function family uses a common qualification (though you can pass - // empty string to supress it). For an unqualified name (doesn't not - // contain a dot) the qualified version is added automatically. A name - // containing a leading dot is a shortcut notation for a qualified-only - // name. - // - explicit - function_family (string qual, function_impl* thunk = &default_thunk) - : qual_ (qual), thunk_ (thunk) {} - - struct entry; - - entry - operator[] (string name) const; - - private: - const string qual_; - function_impl* thunk_; - }; - - // Implementation details. If you can understand and explain all of this, - // then you are hired ;-)! - // - - template - struct function_arg - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return &value_traits::value_type;} - - static T&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - // Use fast but unchecked cast since the caller matched the types. - // - return move (v->as ()); - } - }; - - template <> - struct function_arg // Untyped. - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return nullptr;} - - static names&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - return move (v->as ()); - } - }; - - template <> - struct function_arg // Anytyped. - { - static const bool null = false; - static const bool opt = false; - - static constexpr optional - type () {return nullopt;} - - static value&& - cast (value* v) - { - if (v->null) - throw invalid_argument ("null value"); - - return move (*v); - } - }; - - template - struct function_arg: function_arg - { - static const bool null = true; - - static T* - cast (value* v) - { - if (v->null) - return nullptr; - - // This looks bizarre but makes sense. The cast() that we are calling - // returns an r-value reference to (what's inside) v. And it has to - // return an r-value reference to that the value is moved into by-value - // arguments. - // - T&& r (function_arg::cast (v)); - return &r; - } - }; - - template <> - struct function_arg: function_arg - { - static const bool null = true; - - static value* - cast (value* v) {return v;} // NULL indicator in value::null. - }; - - template - struct function_arg>: function_arg - { - static const bool opt = true; - - static optional - cast (value* v) - { - return v != nullptr ? optional (function_arg::cast (v)) : nullopt; - } - }; - - // Number of optional arguments. Note that we currently don't check that - // they are all at the end. - // - template - struct function_args_opt - { - static const size_t count = (function_arg::opt ? 1 : 0) + - function_args_opt::count; - }; - - template - struct function_args_opt - { - static const size_t count = (function_arg::opt ? 1 : 0); - }; - - // Argument counts/types. - // - template - struct function_args - { - static const size_t max = sizeof...(A); - static const size_t min = max - function_args_opt::count; - - // VC15 doesn't realize that a pointer to static object (in our case it is - // &value_trair::value_type) is constexpr. - // -#if !defined(_MSC_VER) || _MSC_VER > 1910 - static constexpr const optional types[max] = { - function_arg::type ()...}; -#else - static const optional types[max]; -#endif - }; - - template -#if !defined(_MSC_VER) || _MSC_VER > 1910 - constexpr const optional - function_args::types[function_args::max]; -#else - const optional - function_args::types[function_args::max] = { - function_arg::type ()...}; -#endif - - // Specialization for no arguments. - // - template <> - struct function_args<> - { - static const size_t max = 0; - static const size_t min = 0; - -#if !defined(_MSC_VER) || _MSC_VER > 1910 - static constexpr const optional* types = nullptr; -#else - static const optional* const types; -#endif - }; - - // Cast data/thunk. - // - template - struct function_cast - { - // A pointer to a standard layout struct is a pointer to its first data - // member, which in our case is the cast thunk. - // - struct data - { - value (*const thunk) (vector_view, const void*); - R (*const impl) (A...); - }; - - static value - thunk (vector_view args, const void* d) - { - return thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (vector_view args, - R (*impl) (A...), - std::index_sequence) - { - return value ( - impl ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - // Specialization for void return type. In this case we return NULL value. - // - template - struct function_cast - { - struct data - { - value (*const thunk) (vector_view, const void*); - void (*const impl) (A...); - }; - - static value - thunk (vector_view args, const void* d) - { - thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (vector_view args, - void (*impl) (A...), - std::index_sequence) - { - impl (function_arg::cast (i < args.size () ? &args[i] : nullptr)...); - } - }; - - // Customization for coerced lambdas (see below). - // -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (vector_view, const void*); - R (L::*const impl) (A...) const; - }; - - static value - thunk (vector_view args, const void* d) - { - return thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - } - - template - static value - thunk (vector_view args, - R (L::*impl) (A...) const, - std::index_sequence) - { - const L* l (nullptr); // Undefined behavior. - - return value ( - (l->*impl) ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...)); - } - }; - - template - struct function_cast_lamb - { - struct data - { - value (*const thunk) (vector_view, const void*); - void (L::*const impl) (A...) const; - }; - - static value - thunk (vector_view args, const void* d) - { - thunk (move (args), - static_cast (d)->impl, - std::index_sequence_for ()); - return value (nullptr); - } - - template - static void - thunk (vector_view args, - void (L::*impl) (A...) const, - std::index_sequence) - { - const L* l (nullptr); - (l->*impl) ( - function_arg::cast ( - i < args.size () ? &args[i] : nullptr)...); - } - }; -#endif - - // Customization for member functions. - // - template - struct function_cast_memf - { - struct data - { - value (*const thunk) (vector_view, const void*); - R (T::*const impl) () const; - }; - - static value - thunk (vector_view args, const void* d) - { - auto mf (static_cast (d)->impl); - return value ((function_arg::cast (&args[0]).*mf) ()); - } - }; - - template - struct function_cast_memf - { - struct data - { - value (*const thunk) (vector_view, const void*); - void (T::*const impl) () const; - }; - - static value - thunk (vector_view args, const void* d) - { - auto mf (static_cast (d)->impl); - (function_arg::cast (args[0]).*mf) (); - return value (nullptr); - } - }; - - // Customization for data members. - // - template - struct function_cast_memd - { - struct data - { - value (*const thunk) (vector_view, const void*); - R T::*const impl; - }; - - static value - thunk (vector_view args, const void* d) - { - auto dm (static_cast (d)->impl); - return value (move (function_arg::cast (&args[0]).*dm)); - } - }; - - struct function_family::entry - { - string name; - const string& qual; - function_impl* thunk; - - template - void - operator= (R (*impl) (A...)) && - { - using args = function_args; - using cast = function_cast; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, impl})); - } - - // Support for assigning a (capture-less) lambda. - // - // GCC up until version 6 has a bug (#62052) that is triggered by calling - // a lambda that takes a by-value argument via its "decayed" function - // pointer. To work around this we are not going to decay it and instead - // will call its operator() on NULL pointer; yes, undefined behavior, but - // better than a guaranteed crash. - // -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 - template - void - operator= (const L&) && - { - move (*this).coerce_lambda (&L::operator()); - } - - template - void - coerce_lambda (R (L::*op) (A...) const) && - { - using args = function_args; - using cast = function_cast_lamb; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, op})); - } -#else - template - void - operator= (const L& l) && - { - move (*this).operator= (decay_lambda (&L::operator(), l)); - } - - template - static auto - decay_lambda (R (L::*) (A...) const, const L& l) -> R (*) (A...) - { - return static_cast (l); - } -#endif - - // Support for assigning a pointer to member function (e.g. an accessor). - // - // For now we don't support passing additional (to this) arguments though - // we could probably do that. The issues would be the argument passing - // semantics (e.g., what if it's const&) and the optional/default argument - // handling. - // - template - void - operator= (R (T::*mf) () const) && - { - using args = function_args; - using cast = function_cast_memf; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, mf})); - } - - // Support for assigning a pointer to data member. - // - template - void - operator= (R T::*dm) && - { - using args = function_args; - using cast = function_cast_memd; - - insert (move (name), - function_overload ( - nullptr, - args::min, - args::max, - function_overload::types (args::types, args::max), - thunk, - typename cast::data {&cast::thunk, dm})); - } - - private: - void - insert (string, function_overload) const; - }; - - inline auto function_family:: - operator[] (string name) const -> entry - { - return entry {move (name), qual_, thunk_}; - } -} - -#endif // BUILD2_FUNCTION diff --git a/build2/function.cxx b/build2/function.cxx index eeb332f..9f2fb9c 100644 --- a/build2/function.cxx +++ b/build2/function.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strchr() diff --git a/build2/function.hxx b/build2/function.hxx new file mode 100644 index 0000000..9abd4ef --- /dev/null +++ b/build2/function.hxx @@ -0,0 +1,699 @@ +// file : build2/function.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_FUNCTION_HXX +#define BUILD2_FUNCTION_HXX + +#include // index_sequence +#include // aligned_storage +#include + +#include +#include + +#include +#include + +namespace build2 +{ + // Functions can be overloaded based on types of their arguments but + // arguments can be untyped and a function can elect to accept an argument + // of any type. + // + // Functions can be qualified (e.g, string.length(), path.directory()) and + // unqualified (e.g., length(), directory()). Only functions overloaded on + // static types can be unqualified plus they should also define a qualified + // alias. + // + // Low-level function implementation would be called with a list of values + // as arguments. There is also higher-level, more convenient support for + // defining functions as pointers to functions (including capture-less + // lambdas), pointers to member functions (e.g., string::size()), or + // pointers to data members (e.g., name::type). In this case the build2 + // function types are automatically matched to C++ function types according + // to these rules: + // + // T - statically-typed (value_traits must be defined) + // names - untyped + // value - any type + // T* - NULL-able argument (here T can be names) + // value* - NULL-able any type (never NULL itself, use value::null) + // optional - optional argument (here T can be T*, names, value) + // + // Optional arguments must be last. In case of a failure the function is + // expected to issue diagnostics and throw failed. Note that the arguments + // are conceptually "moved" and can be reused by the implementation. + // + // Normally functions come in families that share a common qualification + // (e.g., string. or path.). The function_family class is a "registrar" + // that simplifies handling of function families. For example: + // + // function_family f ("string"); + // + // // Register length() and string.length(). + // // + // f["length"] = &string::size; + // + // // Register string.max_size(). + // // + // f[".max_size"] = []() {return string ().max_size ();}; + // + // For more examples/ideas, study the existing function families (reside + // in the functions-*.cxx files). + // + struct function_overload; + + using function_impl = value (vector_view, const function_overload&); + + struct function_overload + { + const char* name; // Set to point to key by insert() below. + const char* alt_name; // Alternative name, NULL if none. This is the + // qualified name for unqualified or vice verse. + + // Arguments. + // + // A function can have a number of optional arguments. Arguments can also + // be typed. A non-existent entry in arg_types means a value of any type. + // A NULL entry means an untyped value. + // + // If arg_max equals to arg_variadic, then the function takes an unlimited + // number of arguments. In this case the semantics of arg_min and + // arg_types is unchanged. + // + static const size_t arg_variadic = size_t (~0); + + using types = vector_view>; + + const size_t arg_min; + const size_t arg_max; + const types arg_types; + + // Function implementation. + // + function_impl* const impl; + + // Auxiliary data storage. Note that it is assumed to be POD (no + // destructors, bitwise copy, etc). + // + std::aligned_storage::type data; + static const size_t data_size = sizeof (decltype (data)); + + function_overload () = default; + + function_overload (const char* an, + size_t mi, size_t ma, types ts, + function_impl* im) + : alt_name (an), + arg_min (mi), arg_max (ma), arg_types (move (ts)), + impl (im) {} + + template + function_overload (const char* an, + size_t mi, size_t ma, types ts, + function_impl* im, + D d) + : function_overload (an, mi, ma, move (ts), im) + { + // std::is_pod appears to be broken in VC15 and also in GCC up to + // 5 (pointers to members). + // +#if !((defined(_MSC_VER) && _MSC_VER <= 1910) || \ + (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5)) + static_assert (std::is_pod::value, "type is not POD"); +#endif + static_assert (sizeof (D) <= data_size, "insufficient space"); + new (&data) D (move (d)); + } + }; + + ostream& + operator<< (ostream&, const function_overload&); // Print signature. + + class function_map + { + public: + using map_type = std::unordered_multimap; + using iterator = map_type::iterator; + using const_iterator = map_type::const_iterator; + + iterator + insert (string name, function_overload); + + void + erase (iterator i) {map_.erase (i);} + + value + call (const string& name, vector_view args, const location& l) const + { + return call (name, args, l, true).first; + } + + // As above but do not fail if no match was found (but still do if the + // match is ambiguous). Instead return an indication of whether the call + // was made. Used to issue custom diagnostics when calling internal + // functions. + // + pair + try_call (const string& name, + vector_view args, + const location& l) const + { + return call (name, args, l, false); + } + + iterator + begin () {return map_.begin ();} + + iterator + end () {return map_.end ();} + + const_iterator + begin () const {return map_.begin ();} + + const_iterator + end () const {return map_.end ();} + + private: + pair + call (const string&, vector_view, const location&, bool fail) const; + + map_type map_; + }; + + extern function_map functions; + + class function_family + { + public: + // The default thunk catches invalid_argument and issues diagnostics + // by assuming it is related to function arguments and contains useful + // description. + // + // In order to implement a custom thunk (e.g., to catch additional extra + // exceptions), you would normally call the default implementation. + // + static value + default_thunk (vector_view, const function_overload&); + + // A function family uses a common qualification (though you can pass + // empty string to supress it). For an unqualified name (doesn't not + // contain a dot) the qualified version is added automatically. A name + // containing a leading dot is a shortcut notation for a qualified-only + // name. + // + explicit + function_family (string qual, function_impl* thunk = &default_thunk) + : qual_ (qual), thunk_ (thunk) {} + + struct entry; + + entry + operator[] (string name) const; + + private: + const string qual_; + function_impl* thunk_; + }; + + // Implementation details. If you can understand and explain all of this, + // then you are hired ;-)! + // + + template + struct function_arg + { + static const bool null = false; + static const bool opt = false; + + static constexpr optional + type () {return &value_traits::value_type;} + + static T&& + cast (value* v) + { + if (v->null) + throw invalid_argument ("null value"); + + // Use fast but unchecked cast since the caller matched the types. + // + return move (v->as ()); + } + }; + + template <> + struct function_arg // Untyped. + { + static const bool null = false; + static const bool opt = false; + + static constexpr optional + type () {return nullptr;} + + static names&& + cast (value* v) + { + if (v->null) + throw invalid_argument ("null value"); + + return move (v->as ()); + } + }; + + template <> + struct function_arg // Anytyped. + { + static const bool null = false; + static const bool opt = false; + + static constexpr optional + type () {return nullopt;} + + static value&& + cast (value* v) + { + if (v->null) + throw invalid_argument ("null value"); + + return move (*v); + } + }; + + template + struct function_arg: function_arg + { + static const bool null = true; + + static T* + cast (value* v) + { + if (v->null) + return nullptr; + + // This looks bizarre but makes sense. The cast() that we are calling + // returns an r-value reference to (what's inside) v. And it has to + // return an r-value reference to that the value is moved into by-value + // arguments. + // + T&& r (function_arg::cast (v)); + return &r; + } + }; + + template <> + struct function_arg: function_arg + { + static const bool null = true; + + static value* + cast (value* v) {return v;} // NULL indicator in value::null. + }; + + template + struct function_arg>: function_arg + { + static const bool opt = true; + + static optional + cast (value* v) + { + return v != nullptr ? optional (function_arg::cast (v)) : nullopt; + } + }; + + // Number of optional arguments. Note that we currently don't check that + // they are all at the end. + // + template + struct function_args_opt + { + static const size_t count = (function_arg::opt ? 1 : 0) + + function_args_opt::count; + }; + + template + struct function_args_opt + { + static const size_t count = (function_arg::opt ? 1 : 0); + }; + + // Argument counts/types. + // + template + struct function_args + { + static const size_t max = sizeof...(A); + static const size_t min = max - function_args_opt::count; + + // VC15 doesn't realize that a pointer to static object (in our case it is + // &value_trair::value_type) is constexpr. + // +#if !defined(_MSC_VER) || _MSC_VER > 1910 + static constexpr const optional types[max] = { + function_arg::type ()...}; +#else + static const optional types[max]; +#endif + }; + + template +#if !defined(_MSC_VER) || _MSC_VER > 1910 + constexpr const optional + function_args::types[function_args::max]; +#else + const optional + function_args::types[function_args::max] = { + function_arg::type ()...}; +#endif + + // Specialization for no arguments. + // + template <> + struct function_args<> + { + static const size_t max = 0; + static const size_t min = 0; + +#if !defined(_MSC_VER) || _MSC_VER > 1910 + static constexpr const optional* types = nullptr; +#else + static const optional* const types; +#endif + }; + + // Cast data/thunk. + // + template + struct function_cast + { + // A pointer to a standard layout struct is a pointer to its first data + // member, which in our case is the cast thunk. + // + struct data + { + value (*const thunk) (vector_view, const void*); + R (*const impl) (A...); + }; + + static value + thunk (vector_view args, const void* d) + { + return thunk (move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + } + + template + static value + thunk (vector_view args, + R (*impl) (A...), + std::index_sequence) + { + return value ( + impl ( + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...)); + } + }; + + // Specialization for void return type. In this case we return NULL value. + // + template + struct function_cast + { + struct data + { + value (*const thunk) (vector_view, const void*); + void (*const impl) (A...); + }; + + static value + thunk (vector_view args, const void* d) + { + thunk (move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + return value (nullptr); + } + + template + static void + thunk (vector_view args, + void (*impl) (A...), + std::index_sequence) + { + impl (function_arg::cast (i < args.size () ? &args[i] : nullptr)...); + } + }; + + // Customization for coerced lambdas (see below). + // +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 + template + struct function_cast_lamb + { + struct data + { + value (*const thunk) (vector_view, const void*); + R (L::*const impl) (A...) const; + }; + + static value + thunk (vector_view args, const void* d) + { + return thunk (move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + } + + template + static value + thunk (vector_view args, + R (L::*impl) (A...) const, + std::index_sequence) + { + const L* l (nullptr); // Undefined behavior. + + return value ( + (l->*impl) ( + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...)); + } + }; + + template + struct function_cast_lamb + { + struct data + { + value (*const thunk) (vector_view, const void*); + void (L::*const impl) (A...) const; + }; + + static value + thunk (vector_view args, const void* d) + { + thunk (move (args), + static_cast (d)->impl, + std::index_sequence_for ()); + return value (nullptr); + } + + template + static void + thunk (vector_view args, + void (L::*impl) (A...) const, + std::index_sequence) + { + const L* l (nullptr); + (l->*impl) ( + function_arg::cast ( + i < args.size () ? &args[i] : nullptr)...); + } + }; +#endif + + // Customization for member functions. + // + template + struct function_cast_memf + { + struct data + { + value (*const thunk) (vector_view, const void*); + R (T::*const impl) () const; + }; + + static value + thunk (vector_view args, const void* d) + { + auto mf (static_cast (d)->impl); + return value ((function_arg::cast (&args[0]).*mf) ()); + } + }; + + template + struct function_cast_memf + { + struct data + { + value (*const thunk) (vector_view, const void*); + void (T::*const impl) () const; + }; + + static value + thunk (vector_view args, const void* d) + { + auto mf (static_cast (d)->impl); + (function_arg::cast (args[0]).*mf) (); + return value (nullptr); + } + }; + + // Customization for data members. + // + template + struct function_cast_memd + { + struct data + { + value (*const thunk) (vector_view, const void*); + R T::*const impl; + }; + + static value + thunk (vector_view args, const void* d) + { + auto dm (static_cast (d)->impl); + return value (move (function_arg::cast (&args[0]).*dm)); + } + }; + + struct function_family::entry + { + string name; + const string& qual; + function_impl* thunk; + + template + void + operator= (R (*impl) (A...)) && + { + using args = function_args; + using cast = function_cast; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, impl})); + } + + // Support for assigning a (capture-less) lambda. + // + // GCC up until version 6 has a bug (#62052) that is triggered by calling + // a lambda that takes a by-value argument via its "decayed" function + // pointer. To work around this we are not going to decay it and instead + // will call its operator() on NULL pointer; yes, undefined behavior, but + // better than a guaranteed crash. + // +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 6 + template + void + operator= (const L&) && + { + move (*this).coerce_lambda (&L::operator()); + } + + template + void + coerce_lambda (R (L::*op) (A...) const) && + { + using args = function_args; + using cast = function_cast_lamb; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, op})); + } +#else + template + void + operator= (const L& l) && + { + move (*this).operator= (decay_lambda (&L::operator(), l)); + } + + template + static auto + decay_lambda (R (L::*) (A...) const, const L& l) -> R (*) (A...) + { + return static_cast (l); + } +#endif + + // Support for assigning a pointer to member function (e.g. an accessor). + // + // For now we don't support passing additional (to this) arguments though + // we could probably do that. The issues would be the argument passing + // semantics (e.g., what if it's const&) and the optional/default argument + // handling. + // + template + void + operator= (R (T::*mf) () const) && + { + using args = function_args; + using cast = function_cast_memf; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, mf})); + } + + // Support for assigning a pointer to data member. + // + template + void + operator= (R T::*dm) && + { + using args = function_args; + using cast = function_cast_memd; + + insert (move (name), + function_overload ( + nullptr, + args::min, + args::max, + function_overload::types (args::types, args::max), + thunk, + typename cast::data {&cast::thunk, dm})); + } + + private: + void + insert (string, function_overload) const; + }; + + inline auto function_family:: + operator[] (string name) const -> entry + { + return entry {move (name), qual_, thunk_}; + } +} + +#endif // BUILD2_FUNCTION_HXX diff --git a/build2/functions-builtin.cxx b/build2/functions-builtin.cxx index 17ea125..81a190b 100644 --- a/build2/functions-builtin.cxx +++ b/build2/functions-builtin.cxx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include using namespace std; diff --git a/build2/functions-path.cxx b/build2/functions-path.cxx index 8b88ba0..6fff94d 100644 --- a/build2/functions-path.cxx +++ b/build2/functions-path.cxx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include using namespace std; diff --git a/build2/functions-process-path.cxx b/build2/functions-process-path.cxx index 17b6da3..1b7459b 100644 --- a/build2/functions-process-path.cxx +++ b/build2/functions-process-path.cxx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include using namespace std; diff --git a/build2/functions-string.cxx b/build2/functions-string.cxx index 0f28526..2faad06 100644 --- a/build2/functions-string.cxx +++ b/build2/functions-string.cxx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include using namespace std; diff --git a/build2/functions-target-triplet.cxx b/build2/functions-target-triplet.cxx index fb95331..0cb6846 100644 --- a/build2/functions-target-triplet.cxx +++ b/build2/functions-target-triplet.cxx @@ -2,8 +2,8 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include -#include +#include +#include using namespace std; diff --git a/build2/install/init b/build2/install/init deleted file mode 100644 index 9a16111..0000000 --- a/build2/install/init +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/install/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_INSTALL_INIT -#define BUILD2_INSTALL_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace install - { - void - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_INSTALL_INIT diff --git a/build2/install/init.cxx b/build2/install/init.cxx index 0966d82..6a5114d 100644 --- a/build2/install/init.cxx +++ b/build2/install/init.cxx @@ -2,19 +2,19 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/install/init.hxx b/build2/install/init.hxx new file mode 100644 index 0000000..98b4205 --- /dev/null +++ b/build2/install/init.hxx @@ -0,0 +1,31 @@ +// file : build2/install/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_INIT_HXX +#define BUILD2_INSTALL_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + void + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_INSTALL_INIT_HXX diff --git a/build2/install/operation b/build2/install/operation deleted file mode 100644 index 02d406e..0000000 --- a/build2/install/operation +++ /dev/null @@ -1,22 +0,0 @@ -// file : build2/install/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_INSTALL_OPERATION -#define BUILD2_INSTALL_OPERATION - -#include -#include - -#include - -namespace build2 -{ - namespace install - { - extern const operation_info install; - extern const operation_info uninstall; - } -} - -#endif // BUILD2_INSTALL_OPERATION diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx index 44d257f..5405c63 100644 --- a/build2/install/operation.cxx +++ b/build2/install/operation.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; using namespace butl; diff --git a/build2/install/operation.hxx b/build2/install/operation.hxx new file mode 100644 index 0000000..1529621 --- /dev/null +++ b/build2/install/operation.hxx @@ -0,0 +1,22 @@ +// file : build2/install/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_OPERATION_HXX +#define BUILD2_INSTALL_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + extern const operation_info install; + extern const operation_info uninstall; + } +} + +#endif // BUILD2_INSTALL_OPERATION_HXX diff --git a/build2/install/rule b/build2/install/rule deleted file mode 100644 index c923db9..0000000 --- a/build2/install/rule +++ /dev/null @@ -1,101 +0,0 @@ -// file : build2/install/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_INSTALL_RULE -#define BUILD2_INSTALL_RULE - -#include -#include - -#include -#include -#include - -namespace build2 -{ - namespace install - { - class alias_rule: public rule - { - public: - alias_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - }; - - struct install_dir; - - class file_rule: public rule - { - public: - file_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - // Return NULL if this prerequisite should be ignored and pointer to its - // target otherwise. The default implementation ignores prerequsites that - // are outside of this target's project. - // - virtual const target* - filter (action, const target&, prerequisite_member) const; - - // Extra installation hooks. - // - using install_dir = install::install_dir; - - virtual void - install_extra (const file&, const install_dir&) const; - - // Return true if anything was uninstalled. - // - virtual bool - uninstall_extra (const file&, const install_dir&) const; - - // Installation "commands". - // - // If verbose is false, then only print the command at verbosity level 2 - // or higher. - // - public: - // Install a symlink: base/link -> target. - // - static void - install_l (const install_dir& base, - const path& target, - const path& link, - bool verbose); - - // Uninstall a file or symlink: - // - // uninstall / rm /.leaf (); name empty - // uninstall rm /; target can be NULL - // - // Return false if nothing has been removed (i.e., the file does not - // exist). - // - static bool - uninstall_f (const install_dir& base, - const file* t, - const path& name, - bool verbose); - - private: - target_state - perform_install (action, const target&) const; - - target_state - perform_uninstall (action, const target&) const; - }; - } -} - -#endif // BUILD2_INSTALL_RULE diff --git a/build2/install/rule.cxx b/build2/install/rule.cxx index 9f668f9..3a56480 100644 --- a/build2/install/rule.cxx +++ b/build2/install/rule.cxx @@ -2,15 +2,15 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // dir_exists(), file_exists() +#include // dir_exists(), file_exists() -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/install/rule.hxx b/build2/install/rule.hxx new file mode 100644 index 0000000..76259c9 --- /dev/null +++ b/build2/install/rule.hxx @@ -0,0 +1,101 @@ +// file : build2/install/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_RULE_HXX +#define BUILD2_INSTALL_RULE_HXX + +#include +#include + +#include +#include +#include + +namespace build2 +{ + namespace install + { + class alias_rule: public rule + { + public: + alias_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + }; + + struct install_dir; + + class file_rule: public rule + { + public: + file_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + // Return NULL if this prerequisite should be ignored and pointer to its + // target otherwise. The default implementation ignores prerequsites that + // are outside of this target's project. + // + virtual const target* + filter (action, const target&, prerequisite_member) const; + + // Extra installation hooks. + // + using install_dir = install::install_dir; + + virtual void + install_extra (const file&, const install_dir&) const; + + // Return true if anything was uninstalled. + // + virtual bool + uninstall_extra (const file&, const install_dir&) const; + + // Installation "commands". + // + // If verbose is false, then only print the command at verbosity level 2 + // or higher. + // + public: + // Install a symlink: base/link -> target. + // + static void + install_l (const install_dir& base, + const path& target, + const path& link, + bool verbose); + + // Uninstall a file or symlink: + // + // uninstall / rm /.leaf (); name empty + // uninstall rm /; target can be NULL + // + // Return false if nothing has been removed (i.e., the file does not + // exist). + // + static bool + uninstall_f (const install_dir& base, + const file* t, + const path& name, + bool verbose); + + private: + target_state + perform_install (action, const target&) const; + + target_state + perform_uninstall (action, const target&) const; + }; + } +} + +#endif // BUILD2_INSTALL_RULE_HXX diff --git a/build2/install/utility b/build2/install/utility deleted file mode 100644 index 239447e..0000000 --- a/build2/install/utility +++ /dev/null @@ -1,57 +0,0 @@ -// file : build2/install/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_INSTALL_UTILITY -#define BUILD2_INSTALL_UTILITY - -#include -#include - -#include - -namespace build2 -{ - namespace install - { - // Set install path, mode for a target type. - // - inline void - install_path (scope& s, const target_type& tt, dir_path d) - { - auto r ( - s.target_vars[tt]["*"].insert ( - var_pool.rw (s).insert ("install"))); - - if (r.second) // Already set by the user? - r.first.get () = path_cast (move (d)); - } - - template - inline void - install_path (scope& s, dir_path d) - { - return install_path (s, T::static_type, move (d)); - } - - inline void - install_mode (scope& s, const target_type& tt, string m) - { - auto r ( - s.target_vars[tt]["*"].insert ( - var_pool.rw (s).insert ("install.mode"))); - - if (r.second) // Already set by the user? - r.first.get () = move (m); - } - - template - inline void - install_mode (scope& s, string m) - { - return install_mode (s, T::static_type, move (m)); - } - } -} - -#endif // BUILD2_INSTALL_UTILITY diff --git a/build2/install/utility.hxx b/build2/install/utility.hxx new file mode 100644 index 0000000..dfd1915 --- /dev/null +++ b/build2/install/utility.hxx @@ -0,0 +1,57 @@ +// file : build2/install/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_INSTALL_UTILITY_HXX +#define BUILD2_INSTALL_UTILITY_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace install + { + // Set install path, mode for a target type. + // + inline void + install_path (scope& s, const target_type& tt, dir_path d) + { + auto r ( + s.target_vars[tt]["*"].insert ( + var_pool.rw (s).insert ("install"))); + + if (r.second) // Already set by the user? + r.first.get () = path_cast (move (d)); + } + + template + inline void + install_path (scope& s, dir_path d) + { + return install_path (s, T::static_type, move (d)); + } + + inline void + install_mode (scope& s, const target_type& tt, string m) + { + auto r ( + s.target_vars[tt]["*"].insert ( + var_pool.rw (s).insert ("install.mode"))); + + if (r.second) // Already set by the user? + r.first.get () = move (m); + } + + template + inline void + install_mode (scope& s, string m) + { + return install_mode (s, T::static_type, move (m)); + } + } +} + +#endif // BUILD2_INSTALL_UTILITY_HXX diff --git a/build2/lexer b/build2/lexer deleted file mode 100644 index 76308e4..0000000 --- a/build2/lexer +++ /dev/null @@ -1,195 +0,0 @@ -// file : build2/lexer -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_LEXER -#define BUILD2_LEXER - -#include - -#include - -#include -#include - -#include -#include - -namespace build2 -{ - // Context-dependent lexing mode. In the value mode we don't treat certain - // characters (e.g., '+', '=') as special so that we can use them in the - // variable values, e.g., 'foo = g++'. In contrast, in the variable mode, we - // restrict certain character (e.g., '/') from appearing in the name. The - // attribute mode is like value except it doesn't treat '{' and '}' as - // special (so we cannot have name groups in attributes). The eval mode is - // used in the evaluation context. Quoted are internal modes and should not - // be set explicitly. - // - // Note that the normal, value, and eval modes split words separated by the - // pair character (to disable pairs one can pass '\0' as a pair character). - // - // The alternnative modes must be set manually. The value mode automatically - // expires after the end of the line. The attribute mode expires after the - // closing ']'. The variable mode expires after the word token. And the eval - // mode expires after the closing ')'. - // - // Note that normally it is only safe to switch mode when the current token - // is not quoted (or, more generally, when you are not in the double-quoted - // mode) unless the mode treats the double-quote as a separator (e.g., - // variable name mode). Failed that your mode (which now will be the top of - // the mode stack) will prevent proper recognition of the closing quote. - // - - // Extendable/inheritable enum-like class. - // - struct lexer_mode: lexer_mode_base - { - using base_type = lexer_mode_base; - - enum - { - normal = base_type::value_next, - variable, - value, - attribute, - eval, - single_quoted, - double_quoted, - buildspec, - - value_next - }; - - lexer_mode () = default; - lexer_mode (value_type v): base_type (v) {} - lexer_mode (base_type v): base_type (v) {} - }; - - class lexer: protected butl::char_scanner - { - public: - // If escape is not NULL then only escape sequences with characters from - // this string are considered "effective escapes" with all others passed - // through as is. Note that the escape string is not copied. - // - lexer (istream& is, const path& name, const char* escapes = nullptr) - : lexer (is, name, escapes, true) {} - - const path& - name () const {return name_;} - - // Note: sets mode for the next token. The second argument can be used to - // specifythe pair separator character (if the mode supports pairs). If - // escapes not specified, then inherit the current mode's (thought a mode - // can also override it). - // - virtual void - mode (lexer_mode, - char pair_separator = '\0', - optional escapes = nullopt); - - // Expire the current mode early. - // - void - expire_mode () {state_.pop ();} - - lexer_mode - mode () const {return state_.top ().mode;} - - char - pair_separator () const {return state_.top ().sep_pair;} - - // Scanner. Note that it is ok to call next() again after getting eos. - // - // If you extend the lexer and add a custom lexer mode, then you must - // override next() and handle the custom mode there. - // - virtual token - next (); - - // Peek at the first character of the next token. Return the character - // or '\0' if the next token will be eos. Also return an indicator of - // whether the next token will be separated. - // - pair - peek_char (); - - protected: - struct state - { - lexer_mode mode; - - char sep_pair; - bool sep_space; // Are whitespaces separators (see skip_spaces())? - bool sep_newline; // Is newline special (see skip_spaces())? - bool quotes; // Recognize quoted fragments. - - const char* escapes; // Effective escape sequences to recognize. - - // Word separator characters. For two-character sequence put the first - // one in sep_first and the second one in the corresponding position of - // sep_second. If it's a single-character sequence, then put space in - // sep_second. If there are multiple sequences that start with the same - // character, then repeat the first character in sep_first. - // - const char* sep_first; - const char* sep_second; - }; - - token - next_eval (); - - token - next_quoted (); - - // Lex a word assuming current is the top state (which may already have - // been "expired" from the top). - // - virtual token - word (state current, bool separated); - - // Return true if we have seen any spaces. Skipped empty lines - // don't count. In other words, we are only interested in spaces - // that are on the same line as the following non-space character. - // - bool - skip_spaces (); - - // Diagnostics. - // - protected: - fail_mark fail; - - // Lexer state. - // - protected: - lexer (istream& is, const path& n, const char* e, bool sm) - : char_scanner (is), fail ("error", &name_), name_ (n), sep_ (false) - { - if (sm) - mode (lexer_mode::normal, '@', e); - } - - const path name_; - std::stack state_; - - bool sep_; // True if we skipped spaces in peek(). - }; -} - -// Diagnostics plumbing. -// -namespace butl // ADL -{ - inline build2::location - get_location (const butl::char_scanner::xchar& c, const void* data) - { - using namespace build2; - - assert (data != nullptr); // E.g., must be &lexer::name_. - return location (static_cast (data), c.line, c.column); - } -} - -#endif // BUILD2_LEXER diff --git a/build2/lexer.cxx b/build2/lexer.cxx index e117f28..09064d2 100644 --- a/build2/lexer.cxx +++ b/build2/lexer.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strchr() diff --git a/build2/lexer.hxx b/build2/lexer.hxx new file mode 100644 index 0000000..e91e730 --- /dev/null +++ b/build2/lexer.hxx @@ -0,0 +1,195 @@ +// file : build2/lexer.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_LEXER_HXX +#define BUILD2_LEXER_HXX + +#include + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + // Context-dependent lexing mode. In the value mode we don't treat certain + // characters (e.g., '+', '=') as special so that we can use them in the + // variable values, e.g., 'foo = g++'. In contrast, in the variable mode, we + // restrict certain character (e.g., '/') from appearing in the name. The + // attribute mode is like value except it doesn't treat '{' and '}' as + // special (so we cannot have name groups in attributes). The eval mode is + // used in the evaluation context. Quoted are internal modes and should not + // be set explicitly. + // + // Note that the normal, value, and eval modes split words separated by the + // pair character (to disable pairs one can pass '\0' as a pair character). + // + // The alternnative modes must be set manually. The value mode automatically + // expires after the end of the line. The attribute mode expires after the + // closing ']'. The variable mode expires after the word token. And the eval + // mode expires after the closing ')'. + // + // Note that normally it is only safe to switch mode when the current token + // is not quoted (or, more generally, when you are not in the double-quoted + // mode) unless the mode treats the double-quote as a separator (e.g., + // variable name mode). Failed that your mode (which now will be the top of + // the mode stack) will prevent proper recognition of the closing quote. + // + + // Extendable/inheritable enum-like class. + // + struct lexer_mode: lexer_mode_base + { + using base_type = lexer_mode_base; + + enum + { + normal = base_type::value_next, + variable, + value, + attribute, + eval, + single_quoted, + double_quoted, + buildspec, + + value_next + }; + + lexer_mode () = default; + lexer_mode (value_type v): base_type (v) {} + lexer_mode (base_type v): base_type (v) {} + }; + + class lexer: protected butl::char_scanner + { + public: + // If escape is not NULL then only escape sequences with characters from + // this string are considered "effective escapes" with all others passed + // through as is. Note that the escape string is not copied. + // + lexer (istream& is, const path& name, const char* escapes = nullptr) + : lexer (is, name, escapes, true) {} + + const path& + name () const {return name_;} + + // Note: sets mode for the next token. The second argument can be used to + // specifythe pair separator character (if the mode supports pairs). If + // escapes not specified, then inherit the current mode's (thought a mode + // can also override it). + // + virtual void + mode (lexer_mode, + char pair_separator = '\0', + optional escapes = nullopt); + + // Expire the current mode early. + // + void + expire_mode () {state_.pop ();} + + lexer_mode + mode () const {return state_.top ().mode;} + + char + pair_separator () const {return state_.top ().sep_pair;} + + // Scanner. Note that it is ok to call next() again after getting eos. + // + // If you extend the lexer and add a custom lexer mode, then you must + // override next() and handle the custom mode there. + // + virtual token + next (); + + // Peek at the first character of the next token. Return the character + // or '\0' if the next token will be eos. Also return an indicator of + // whether the next token will be separated. + // + pair + peek_char (); + + protected: + struct state + { + lexer_mode mode; + + char sep_pair; + bool sep_space; // Are whitespaces separators (see skip_spaces())? + bool sep_newline; // Is newline special (see skip_spaces())? + bool quotes; // Recognize quoted fragments. + + const char* escapes; // Effective escape sequences to recognize. + + // Word separator characters. For two-character sequence put the first + // one in sep_first and the second one in the corresponding position of + // sep_second. If it's a single-character sequence, then put space in + // sep_second. If there are multiple sequences that start with the same + // character, then repeat the first character in sep_first. + // + const char* sep_first; + const char* sep_second; + }; + + token + next_eval (); + + token + next_quoted (); + + // Lex a word assuming current is the top state (which may already have + // been "expired" from the top). + // + virtual token + word (state current, bool separated); + + // Return true if we have seen any spaces. Skipped empty lines + // don't count. In other words, we are only interested in spaces + // that are on the same line as the following non-space character. + // + bool + skip_spaces (); + + // Diagnostics. + // + protected: + fail_mark fail; + + // Lexer state. + // + protected: + lexer (istream& is, const path& n, const char* e, bool sm) + : char_scanner (is), fail ("error", &name_), name_ (n), sep_ (false) + { + if (sm) + mode (lexer_mode::normal, '@', e); + } + + const path name_; + std::stack state_; + + bool sep_; // True if we skipped spaces in peek(). + }; +} + +// Diagnostics plumbing. +// +namespace butl // ADL +{ + inline build2::location + get_location (const butl::char_scanner::xchar& c, const void* data) + { + using namespace build2; + + assert (data != nullptr); // E.g., must be &lexer::name_. + return location (static_cast (data), c.line, c.column); + } +} + +#endif // BUILD2_LEXER_HXX diff --git a/build2/module b/build2/module deleted file mode 100644 index 7c4672f..0000000 --- a/build2/module +++ /dev/null @@ -1,114 +0,0 @@ -// file : build2/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_MODULE -#define BUILD2_MODULE - -#include - -#include -#include - -#include -#include - -namespace build2 -{ - class scope; - class location; - - class module_base - { - public: - virtual - ~module_base () = default; - }; - - using module_boot_function = - void (scope& root, - const location&, - unique_ptr&); - - // Return false if the module configuration (normally based on the default - // values) was unsuccessful but this is not (yet) an error. One example - // would be the optional use of a module. Or a module might remain - // unconfigured for as long as it is actually not used (e.g., install, - // dist). The return value is used to set the .configured variable. - // - using module_init_function = - bool (scope& root, - scope& base, - const location&, - unique_ptr&, - bool first, // First time for this project. - bool optional, // Loaded with using? (optional module). - const variable_map& hints); // Configuration hints (see below). - - struct module_functions - { - module_boot_function* boot; - module_init_function* init; - }; - - // The register() function will be written in C++ and will be called from - // C++ but we need to suppress name mangling to be able to use dlsym() and - // equivalent. - // - extern "C" - using module_register_function = module_functions (); - - // Loaded modules state. - // - struct module_state - { - bool boot; // True if the module boot'ed but not yet init'ed. - module_init_function* init; - unique_ptr module; - const location loc; // Boot location. - }; - - struct loaded_module_map: std::map - { - template - T* - lookup (const string& name) const - { - auto i (find (name)); - return i != end () - ? static_cast (i->second.module.get ()) - : nullptr; - } - }; - - // Load and boot the specified module. - // - void - boot_module (scope& root, const string& name, const location&); - - // Load (if not already loaded) and initialize the specified module. Used - // by the parser but also by some modules to load prerequisite modules. - // Return true if the module was both successfully loaded and configured - // (false can only be returned if optional). - // - // The config_hints variable map can be used to pass configuration hints - // from one module to another. For example, the cxx modude may pass the - // target platform (which was extracted from the C++ compiler) to the bin - // module (which may not always be able to extract the same information from - // its tools). - // - bool - load_module (scope& root, - scope& base, - const string& name, - const location&, - bool optional = false, - const variable_map& config_hints = variable_map ()); - - // Builtin modules. - // - using available_module_map = std::map; - extern available_module_map builtin_modules; -} - -#endif // BUILD2_MODULE diff --git a/build2/module.cxx b/build2/module.cxx index 6fc33fa..f1e7dd3 100644 --- a/build2/module.cxx +++ b/build2/module.cxx @@ -2,11 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; diff --git a/build2/module.hxx b/build2/module.hxx new file mode 100644 index 0000000..e3ab94f --- /dev/null +++ b/build2/module.hxx @@ -0,0 +1,114 @@ +// file : build2/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_MODULE_HXX +#define BUILD2_MODULE_HXX + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + class scope; + class location; + + class module_base + { + public: + virtual + ~module_base () = default; + }; + + using module_boot_function = + void (scope& root, + const location&, + unique_ptr&); + + // Return false if the module configuration (normally based on the default + // values) was unsuccessful but this is not (yet) an error. One example + // would be the optional use of a module. Or a module might remain + // unconfigured for as long as it is actually not used (e.g., install, + // dist). The return value is used to set the .configured variable. + // + using module_init_function = + bool (scope& root, + scope& base, + const location&, + unique_ptr&, + bool first, // First time for this project. + bool optional, // Loaded with using? (optional module). + const variable_map& hints); // Configuration hints (see below). + + struct module_functions + { + module_boot_function* boot; + module_init_function* init; + }; + + // The register() function will be written in C++ and will be called from + // C++ but we need to suppress name mangling to be able to use dlsym() and + // equivalent. + // + extern "C" + using module_register_function = module_functions (); + + // Loaded modules state. + // + struct module_state + { + bool boot; // True if the module boot'ed but not yet init'ed. + module_init_function* init; + unique_ptr module; + const location loc; // Boot location. + }; + + struct loaded_module_map: std::map + { + template + T* + lookup (const string& name) const + { + auto i (find (name)); + return i != end () + ? static_cast (i->second.module.get ()) + : nullptr; + } + }; + + // Load and boot the specified module. + // + void + boot_module (scope& root, const string& name, const location&); + + // Load (if not already loaded) and initialize the specified module. Used + // by the parser but also by some modules to load prerequisite modules. + // Return true if the module was both successfully loaded and configured + // (false can only be returned if optional). + // + // The config_hints variable map can be used to pass configuration hints + // from one module to another. For example, the cxx modude may pass the + // target platform (which was extracted from the C++ compiler) to the bin + // module (which may not always be able to extract the same information from + // its tools). + // + bool + load_module (scope& root, + scope& base, + const string& name, + const location&, + bool optional = false, + const variable_map& config_hints = variable_map ()); + + // Builtin modules. + // + using available_module_map = std::map; + extern available_module_map builtin_modules; +} + +#endif // BUILD2_MODULE_HXX diff --git a/build2/name b/build2/name deleted file mode 100644 index ccceebe..0000000 --- a/build2/name +++ /dev/null @@ -1,158 +0,0 @@ -// file : build2/name -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -// Note: include instead of this file directly. -// - -#ifndef BUILD2_NAME -#define BUILD2_NAME - -// We cannot include since it includes . -// -#include // move() - -namespace build2 -{ - using std::move; - - // A name is what we operate on by default. Depending on the context, it can - // be interpreted as a target or prerequisite name. A name without a type - // and directory can be used to represent any text. A name with directory - // and empty value represents a directory. - // - // A name may also be qualified with a project. If the project name is - // empty, then it means the name is in a project other than our own (e.g., - // it is installed). - // - // A type or project can only be specified if either directory or value are - // not empty. - // - // If pair is not '\0', then this name and the next in the list form a - // pair. Can be used as a bool flag. - // - struct name - { - optional proj; - dir_path dir; - string type; - string value; - char pair = '\0'; - - name () = default; - name (string v): value (move (v)) {} - name (dir_path d): dir (move (d)) {} - name (string t, string v): type (move (t)), value (move (v)) {} - - name (dir_path d, string t, string v) - : dir (move (d)), type (move (t)), value (move (v)) {} - - name (optional p, dir_path d, string t, string v) - : proj (move (p)), dir (move (d)), type (move (t)), value (move (v)) {} - - bool - qualified () const {return proj.has_value ();} - - bool - unqualified () const {return !qualified ();} - - bool - typed () const {return !type.empty ();} - - bool - untyped () const {return type.empty ();} - - // Note: if dir and value are empty then there should be no proj or type. - // - bool - empty () const {return dir.empty () && value.empty ();} - - // Note that strictly speaking the following tests should be orthogonal - // to qualification. However, the vast majority of cases where we expect - // a simple or directory name, we also expect it to be unqualified. - // - // Note also that empty name is simple but not a directory. - // - bool - simple (bool ignore_qual = false) const - { - return (ignore_qual || unqualified ()) && untyped () && dir.empty (); - } - - bool - directory (bool ignore_qual = false) const - { - return (ignore_qual || unqualified ()) && - untyped () && !dir.empty () && value.empty (); - } - - int - compare (const name&) const; - }; - - inline bool - operator== (const name& x, const name& y) {return x.compare (y) == 0;} - - inline bool - operator!= (const name& x, const name& y) {return !(x == y);} - - inline bool - operator< (const name& x, const name& y) {return x.compare (y) < 0;} - - // Return string representation of a name. - // - string - to_string (const name&); - - // Serialize the name to the stream. If requested, the name components - // containing special characters are quoted. The special characters are: - // - // {}[]$() \t\n#\"'% - // - // If the pair argument is not '\0', then it is added to the above special - // characters set. If the quote character is present in the component then - // it is double quoted rather than single quoted. In this case the following - // characters are escaped: - // - // \$(" - // - // Note that in the quoted mode empty unqualified name is printed as '', - // not {}. - // - ostream& - to_stream (ostream&, const name&, bool quote, char pair = '\0'); - - inline ostream& - operator<< (ostream& os, const name& n) {return to_stream (os, n, false);} - - // Vector of names. - // - // Quote often it will contain just one element so we use small_vector<1>. - // Note also that it must be a separate type rather than an alias for - // vector in order to distinguish between untyped variable values - // (names) and typed ones (vector). - // - using names = small_vector; - - using names_view = vector_view; - - // The same semantics as to_stream(name). - // - ostream& - to_stream (ostream&, const names_view&, bool quote, char pair = '\0'); - - inline ostream& - operator<< (ostream& os, const names_view& ns) { - return to_stream (os, ns, false);} - - inline ostream& - operator<< (ostream& os, const names& ns) {return os << names_view (ns);} - - // Pair of names. - // - using name_pair = pair; -} - -#include - -#endif // BUILD2_NAME diff --git a/build2/name.cxx b/build2/name.cxx index 0b3d00f..27733af 100644 --- a/build2/name.cxx +++ b/build2/name.cxx @@ -2,13 +2,13 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // Note: not +#include // Note: not #include // strchr() #include -#include +#include namespace build2 { diff --git a/build2/name.hxx b/build2/name.hxx new file mode 100644 index 0000000..c341cc9 --- /dev/null +++ b/build2/name.hxx @@ -0,0 +1,158 @@ +// file : build2/name.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +// Note: include instead of this file directly. +// + +#ifndef BUILD2_NAME_HXX +#define BUILD2_NAME_HXX + +// We cannot include since it includes . +// +#include // move() + +namespace build2 +{ + using std::move; + + // A name is what we operate on by default. Depending on the context, it can + // be interpreted as a target or prerequisite name. A name without a type + // and directory can be used to represent any text. A name with directory + // and empty value represents a directory. + // + // A name may also be qualified with a project. If the project name is + // empty, then it means the name is in a project other than our own (e.g., + // it is installed). + // + // A type or project can only be specified if either directory or value are + // not empty. + // + // If pair is not '\0', then this name and the next in the list form a + // pair. Can be used as a bool flag. + // + struct name + { + optional proj; + dir_path dir; + string type; + string value; + char pair = '\0'; + + name () = default; + name (string v): value (move (v)) {} + name (dir_path d): dir (move (d)) {} + name (string t, string v): type (move (t)), value (move (v)) {} + + name (dir_path d, string t, string v) + : dir (move (d)), type (move (t)), value (move (v)) {} + + name (optional p, dir_path d, string t, string v) + : proj (move (p)), dir (move (d)), type (move (t)), value (move (v)) {} + + bool + qualified () const {return proj.has_value ();} + + bool + unqualified () const {return !qualified ();} + + bool + typed () const {return !type.empty ();} + + bool + untyped () const {return type.empty ();} + + // Note: if dir and value are empty then there should be no proj or type. + // + bool + empty () const {return dir.empty () && value.empty ();} + + // Note that strictly speaking the following tests should be orthogonal + // to qualification. However, the vast majority of cases where we expect + // a simple or directory name, we also expect it to be unqualified. + // + // Note also that empty name is simple but not a directory. + // + bool + simple (bool ignore_qual = false) const + { + return (ignore_qual || unqualified ()) && untyped () && dir.empty (); + } + + bool + directory (bool ignore_qual = false) const + { + return (ignore_qual || unqualified ()) && + untyped () && !dir.empty () && value.empty (); + } + + int + compare (const name&) const; + }; + + inline bool + operator== (const name& x, const name& y) {return x.compare (y) == 0;} + + inline bool + operator!= (const name& x, const name& y) {return !(x == y);} + + inline bool + operator< (const name& x, const name& y) {return x.compare (y) < 0;} + + // Return string representation of a name. + // + string + to_string (const name&); + + // Serialize the name to the stream. If requested, the name components + // containing special characters are quoted. The special characters are: + // + // {}[]$() \t\n#\"'% + // + // If the pair argument is not '\0', then it is added to the above special + // characters set. If the quote character is present in the component then + // it is double quoted rather than single quoted. In this case the following + // characters are escaped: + // + // \$(" + // + // Note that in the quoted mode empty unqualified name is printed as '', + // not {}. + // + ostream& + to_stream (ostream&, const name&, bool quote, char pair = '\0'); + + inline ostream& + operator<< (ostream& os, const name& n) {return to_stream (os, n, false);} + + // Vector of names. + // + // Quote often it will contain just one element so we use small_vector<1>. + // Note also that it must be a separate type rather than an alias for + // vector in order to distinguish between untyped variable values + // (names) and typed ones (vector). + // + using names = small_vector; + + using names_view = vector_view; + + // The same semantics as to_stream(name). + // + ostream& + to_stream (ostream&, const names_view&, bool quote, char pair = '\0'); + + inline ostream& + operator<< (ostream& os, const names_view& ns) { + return to_stream (os, ns, false);} + + inline ostream& + operator<< (ostream& os, const names& ns) {return os << names_view (ns);} + + // Pair of names. + // + using name_pair = pair; +} + +#include + +#endif // BUILD2_NAME_HXX diff --git a/build2/operation b/build2/operation deleted file mode 100644 index 3558c0b..0000000 --- a/build2/operation +++ /dev/null @@ -1,435 +0,0 @@ -// file : build2/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_OPERATION -#define BUILD2_OPERATION - -#include - -#include -#include - -#include - -namespace build2 -{ - class location; - class scope; - class target_key; - struct opspec; - - // While we are using uint8_t for the meta/operation ids, we assume - // that each is limited to 4 bits (max 128 entries) so that we can - // store the combined action id in uint8_t as well. This makes our - // life easier when it comes to defining switch labels for action - // ids (no need to mess with endian-ness). - // - // Note that 0 is not a valid meta/operation/action id. - // - using meta_operation_id = uint8_t; - using operation_id = uint8_t; - using action_id = uint8_t; - - // Meta-operations and operations are not the end of the story. We - // also have operation nesting (currently only one level deep) which - // is used to implement pre/post operations (currently, but may be - // useful for other things). Here is the idea: the test operation - // needs to make sure that the targets that it needs to test are - // up-to-date. So it runs update as its pre-operation. It is almost - // like an ordinary update except that it has test as its outer - // operation (the meta-operations are always the same). This way a - // rule can recognize that this is "update for test" and do something - // differently. For example, if an executable is not a test, then - // there is no use updating it. At the same time, most rules will - // ignore the fact that this is a nested update and for them it is - // "update as usual". - // - struct action - { - action (): inner_id (0), outer_id (0) {} // Invalid action. - - bool - valid () const {return inner_id != 0;} - - // If this is not a nested operation, then outer should be 0. - // - action (meta_operation_id m, operation_id inner, operation_id outer = 0) - : inner_id ((m << 4) | inner), - outer_id (outer == 0 ? 0 : (m << 4) | outer) {} - - meta_operation_id - meta_operation () const {return inner_id >> 4;} - - operation_id - operation () const {return inner_id & 0xF;} - - operation_id - outer_operation () const {return outer_id & 0xF;} - - // Implicit conversion operator to action_id for the switch() statement, - // etc. Most places only care about the inner operation. - // - operator action_id () const {return inner_id;} - - action_id inner_id; - action_id outer_id; - }; - - // This is an "overrides" comparison, i.e., it returns true if the recipe - // for x overrides recipe for y. The idea is that for the same inner - // operation, action with an outer operation is "weaker" than the one - // without. - // - inline bool - operator> (action x, action y) - { - return x.inner_id != y.inner_id || - (x.outer_id != y.outer_id && y.outer_id != 0); - } - - inline bool - operator== (action x, action y) - { - return x.inner_id == y.inner_id && x.outer_id == y.outer_id; - } - - inline bool - operator!= (action x, action y) {return !(x == y);} - - ostream& - operator<< (ostream&, action); - - // Id constants for build-in and pre-defined meta/operations. - // - const meta_operation_id noop_id = 1; // nomop? - const meta_operation_id perform_id = 2; - const meta_operation_id configure_id = 3; - const meta_operation_id disfigure_id = 4; - const meta_operation_id create_id = 5; - const meta_operation_id dist_id = 6; - - // The default operation is a special marker that can be used to indicate - // that no operation was explicitly specified by the user. If adding - // something here remember to update the man page. - // - const operation_id default_id = 1; // Shall be first. - const operation_id update_id = 2; // Shall be second. - const operation_id clean_id = 3; - const operation_id test_id = 4; - const operation_id install_id = 5; - const operation_id uninstall_id = 6; - - const action_id perform_update_id = (perform_id << 4) | update_id; - const action_id perform_clean_id = (perform_id << 4) | clean_id; - const action_id perform_test_id = (perform_id << 4) | test_id; - const action_id perform_install_id = (perform_id << 4) | install_id; - const action_id perform_uninstall_id = (perform_id << 4) | uninstall_id; - - const action_id configure_update_id = (configure_id << 4) | update_id; - - // Recipe execution mode. - // - // When a target is a prerequisite of another target, its recipe can be - // executed before the dependent's recipe (the normal case) or after. - // We will call these "front" and "back" execution modes, respectively - // (think "the prerequisite is 'front-running' the dependent"). - // - // There could also be several dependent targets and the prerequisite's - // recipe can be execute as part of the first dependent (the normal - // case) or last (or for all/some of them; see the recipe execution - // protocol in ). We will call these "first" and "last" - // execution modes, respectively. - // - // Now you may be having a hard time imagining where a mode other than - // the normal one (first/front) could be useful. An the answer is, - // compensating or inverse operations such as clean, uninstall, etc. - // If we use the last/back mode for, say, clean, then we will remove - // targets in the order inverse to the way they were updated. While - // this sounds like an elegant idea, are there any practical benefits - // of doing it this way? As it turns out there is (at least) one: when - // we are removing a directory (see fsdir{}), we want to do it after - // all the targets that depend on it (such as files, sub-directories) - // were removed. If we do it before, then the directory won't be empty - // yet. - // - // It appears that this execution mode is dictated by the essence of - // the operation. Constructive operations (those that "do") seem to - // naturally use the first/front mode. That is, we need to "do" the - // prerequisite first before we can "do" the dependent. While the - // destructive ones (those that "undo") seem to need last/back. That - // is, we need to "undo" all the dependents before we can "undo" the - // prerequisite (say, we need to remove all the files before we can - // remove their directory). - // - // If you noticed the parallel with the way C++ construction and - // destruction works for base/derived object then you earned a gold - // star! - // - // Note that the front/back mode is realized in the dependen's recipe - // (which is another indication that it is a property of the operation). - // - enum class execution_mode {first, last}; - - // Meta-operation info. - // - - // Normally a list of resolved and matched targets to execute. But can be - // something else, depending on the meta-operation. - // - typedef vector action_targets; - - struct meta_operation_info - { - const meta_operation_id id; - const string name; - - // Name derivatives for diagnostics. If empty, then the meta- - // operation need not be mentioned. - // - const string name_do; // E.g., [to] 'configure'. - const string name_doing; // E.g., [while] 'configuring'. - const string name_did; // E.g., 'configured'. - const string name_done; // E.g., 'is configured'. - - // The first argument in all the callback is the meta-operation - // parameters. - // - // If the meta-operation expects parameters, then it should have a - // non-NULL meta_operation_pre(). Failed that, any parameters will be - // diagnosed as unexpected. - - // Start of meta-operation and operation batches. - // - // If operation_pre() is not NULL, then it may translate default_id - // (and only default_id) to some other operation. If not translated, - // then default_id is used. If, however, operation_pre() is NULL, - // then default_id is translated to update_id. - // - void (*meta_operation_pre) (const values&, const location&); - operation_id (*operation_pre) (const values&, operation_id); - - // Meta-operation-specific logic to load the buildfile, search and match - // the targets, and execute the action on the targets. - // - void (*load) (const values&, - scope& root, - const path& buildfile, - const dir_path& out_base, - const dir_path& src_base, - const location&); - - void (*search) (const values&, - const scope& root, - const scope& base, - const target_key&, - const location&, - action_targets&); - - void (*match) (const values&, action, action_targets&); - - void (*execute) (const values&, action, action_targets&, bool quiet); - - // Start of operation and meta-operation batches. - // - void (*operation_post) (const values&, operation_id); - void (*meta_operation_post) (const values&); - }; - - // Built-in meta-operations. - // - - // perform - // - - // Load the buildfile. This is the default implementation that first - // calls root_pre(), then creates the scope for out_base, and, finally, - // loads the buildfile unless it has already been loaded for the root - // scope. - // - void - load (const values&, - scope&, - const path&, - const dir_path&, - const dir_path&, - const location&); - - // Search and match the target. This is the default implementation - // that does just that and adds a pointer to the target to the list. - // - void - search (const values&, - const scope&, - const scope&, - const target_key&, - const location&, - action_targets&); - - void - match (const values&, action, action_targets&); - - // Execute the action on the list of targets. This is the default - // implementation that does just that while issuing appropriate - // diagnostics (unless quiet). - // - void - execute (const values&, action, const action_targets&, bool quiet); - - extern const meta_operation_info noop; - extern const meta_operation_info perform; - - // Operation info. - // - struct operation_info - { - const operation_id id; - const string name; - - // Name derivatives for diagnostics. Note that unlike meta-operations, - // these can only be empty for the default operation (id 1), And - // meta-operations that make use of the default operation shall not - // have empty derivatives (failed which only target name will be - // printed). - // - const string name_do; // E.g., [to] 'update'. - const string name_doing; // E.g., [while] 'updating'. - const string name_did; // E.g., [not] 'updated'. - const string name_done; // E.g., 'is up to date'. - - const execution_mode mode; - - // This is the operation's concurrency multiplier. 0 means run serially, - // 1 means run at hardware concurrency (unless overridden by the user). - // - const size_t concurrency; - - // The first argument in all the callback is the operation parameters. - // - // If the meta-operation expects parameters, then it should have a - // non-NULL pre(). Failed that, any parameters will be diagnosed as - // unexpected. - - // If the returned operation_id's are not 0, then they are injected - // as pre/post operations for this operation. Can be NULL if unused. - // The returned operation_id shall not be default_id. - // - operation_id (*pre) (const values&, meta_operation_id, const location&); - operation_id (*post) (const values&, meta_operation_id); - }; - - // Built-in operations. - // - extern const operation_info default_; - extern const operation_info update; - extern const operation_info clean; - - // Global meta/operation tables. Each registered meta/operation - // is assigned an id which is used as an index in the per-project - // registered meta/operation lists. - // - // We have three types of meta/operations: built-in (e.g., perform, - // update), pre-defined (e.g., configure, test), and dynamically- - // defined. For built-in ones, both the id and implementation are - // part of the build2 core. For pre-defined, the id is registered - // as part of the core but the implementation is loaded as part of - // a module. The idea with pre-defined operations is that they have - // common, well-established semantics but could still be optional. - // Another aspect of pre-defined operations is that often rules - // across multiple modules need to know their ids. Finally, - // dynamically-defined meta/operations have their ids registered - // as part of a module load. In this case, the meta/operation is - // normally (but not necessarily) fully implemented by this module. - // - // Note also that the name of a meta/operation in a sense defines - // its semantics. It would be strange to have an operation called - // test that does two very different things in different projects. - // - // A built-in/pre-defined meta-operation can also provide a pre-processor - // callback that will be called for operation-specs before any project - // discovery/bootstrap is performed. - // - struct meta_operation_data - { - // The processor may modify the parameters, opspec, and change the - // meta-operation by returning a different name. - // - // If lifted is true then the operation name in opspec is bogus (has - // been lifted) and the default/empty name should be assumed instead. - // - using process_func = const string& (const variable_overrides&, - values&, - vector_view&, - bool lifted, - const location&); - - meta_operation_data () = default; - meta_operation_data (const char* n, process_func p = nullptr) - : name (n), process (p) {} - - string name; - process_func* process; - }; - - extern butl::string_table meta_operation_table; - extern butl::string_table operation_table; - - // These are "sparse" in the sense that we may have "holes" that - // are represented as NULL pointers. Also, lookup out of bounds - // is treated as a hole. - // - template - struct sparse_vector - { - using base_type = vector; - using size_type = typename base_type::size_type; - - void - insert (size_type i, T& x) - { - size_type n (v_.size ()); - - if (i < n) - v_[i] = &x; - else - { - if (n != i) - v_.resize (i, nullptr); // Add holes. - v_.push_back (&x); - } - } - - T* - operator[] (size_type i) const - { - return i < v_.size () ? v_[i] : nullptr; - } - - bool - empty () const {return v_.empty ();} - - // Note that this is more of a "max index" rather than size. - // - size_type - size () const {return v_.size ();} - - private: - base_type v_; - }; - - using meta_operations = sparse_vector; - using operations = sparse_vector; -} - -namespace butl -{ - template <> - struct string_table_traits - { - static const std::string& - key (const build2::meta_operation_data& d) {return d.name;} - }; -} - -#endif // BUILD2_OPERATION diff --git a/build2/operation.cxx b/build2/operation.cxx index 2099ec6..7dea25b 100644 --- a/build2/operation.cxx +++ b/build2/operation.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/operation.hxx b/build2/operation.hxx new file mode 100644 index 0000000..08bcccf --- /dev/null +++ b/build2/operation.hxx @@ -0,0 +1,435 @@ +// file : build2/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_OPERATION_HXX +#define BUILD2_OPERATION_HXX + +#include + +#include +#include + +#include + +namespace build2 +{ + class location; + class scope; + class target_key; + struct opspec; + + // While we are using uint8_t for the meta/operation ids, we assume + // that each is limited to 4 bits (max 128 entries) so that we can + // store the combined action id in uint8_t as well. This makes our + // life easier when it comes to defining switch labels for action + // ids (no need to mess with endian-ness). + // + // Note that 0 is not a valid meta/operation/action id. + // + using meta_operation_id = uint8_t; + using operation_id = uint8_t; + using action_id = uint8_t; + + // Meta-operations and operations are not the end of the story. We + // also have operation nesting (currently only one level deep) which + // is used to implement pre/post operations (currently, but may be + // useful for other things). Here is the idea: the test operation + // needs to make sure that the targets that it needs to test are + // up-to-date. So it runs update as its pre-operation. It is almost + // like an ordinary update except that it has test as its outer + // operation (the meta-operations are always the same). This way a + // rule can recognize that this is "update for test" and do something + // differently. For example, if an executable is not a test, then + // there is no use updating it. At the same time, most rules will + // ignore the fact that this is a nested update and for them it is + // "update as usual". + // + struct action + { + action (): inner_id (0), outer_id (0) {} // Invalid action. + + bool + valid () const {return inner_id != 0;} + + // If this is not a nested operation, then outer should be 0. + // + action (meta_operation_id m, operation_id inner, operation_id outer = 0) + : inner_id ((m << 4) | inner), + outer_id (outer == 0 ? 0 : (m << 4) | outer) {} + + meta_operation_id + meta_operation () const {return inner_id >> 4;} + + operation_id + operation () const {return inner_id & 0xF;} + + operation_id + outer_operation () const {return outer_id & 0xF;} + + // Implicit conversion operator to action_id for the switch() statement, + // etc. Most places only care about the inner operation. + // + operator action_id () const {return inner_id;} + + action_id inner_id; + action_id outer_id; + }; + + // This is an "overrides" comparison, i.e., it returns true if the recipe + // for x overrides recipe for y. The idea is that for the same inner + // operation, action with an outer operation is "weaker" than the one + // without. + // + inline bool + operator> (action x, action y) + { + return x.inner_id != y.inner_id || + (x.outer_id != y.outer_id && y.outer_id != 0); + } + + inline bool + operator== (action x, action y) + { + return x.inner_id == y.inner_id && x.outer_id == y.outer_id; + } + + inline bool + operator!= (action x, action y) {return !(x == y);} + + ostream& + operator<< (ostream&, action); + + // Id constants for build-in and pre-defined meta/operations. + // + const meta_operation_id noop_id = 1; // nomop? + const meta_operation_id perform_id = 2; + const meta_operation_id configure_id = 3; + const meta_operation_id disfigure_id = 4; + const meta_operation_id create_id = 5; + const meta_operation_id dist_id = 6; + + // The default operation is a special marker that can be used to indicate + // that no operation was explicitly specified by the user. If adding + // something here remember to update the man page. + // + const operation_id default_id = 1; // Shall be first. + const operation_id update_id = 2; // Shall be second. + const operation_id clean_id = 3; + const operation_id test_id = 4; + const operation_id install_id = 5; + const operation_id uninstall_id = 6; + + const action_id perform_update_id = (perform_id << 4) | update_id; + const action_id perform_clean_id = (perform_id << 4) | clean_id; + const action_id perform_test_id = (perform_id << 4) | test_id; + const action_id perform_install_id = (perform_id << 4) | install_id; + const action_id perform_uninstall_id = (perform_id << 4) | uninstall_id; + + const action_id configure_update_id = (configure_id << 4) | update_id; + + // Recipe execution mode. + // + // When a target is a prerequisite of another target, its recipe can be + // executed before the dependent's recipe (the normal case) or after. + // We will call these "front" and "back" execution modes, respectively + // (think "the prerequisite is 'front-running' the dependent"). + // + // There could also be several dependent targets and the prerequisite's + // recipe can be execute as part of the first dependent (the normal + // case) or last (or for all/some of them; see the recipe execution + // protocol in ). We will call these "first" and "last" + // execution modes, respectively. + // + // Now you may be having a hard time imagining where a mode other than + // the normal one (first/front) could be useful. An the answer is, + // compensating or inverse operations such as clean, uninstall, etc. + // If we use the last/back mode for, say, clean, then we will remove + // targets in the order inverse to the way they were updated. While + // this sounds like an elegant idea, are there any practical benefits + // of doing it this way? As it turns out there is (at least) one: when + // we are removing a directory (see fsdir{}), we want to do it after + // all the targets that depend on it (such as files, sub-directories) + // were removed. If we do it before, then the directory won't be empty + // yet. + // + // It appears that this execution mode is dictated by the essence of + // the operation. Constructive operations (those that "do") seem to + // naturally use the first/front mode. That is, we need to "do" the + // prerequisite first before we can "do" the dependent. While the + // destructive ones (those that "undo") seem to need last/back. That + // is, we need to "undo" all the dependents before we can "undo" the + // prerequisite (say, we need to remove all the files before we can + // remove their directory). + // + // If you noticed the parallel with the way C++ construction and + // destruction works for base/derived object then you earned a gold + // star! + // + // Note that the front/back mode is realized in the dependen's recipe + // (which is another indication that it is a property of the operation). + // + enum class execution_mode {first, last}; + + // Meta-operation info. + // + + // Normally a list of resolved and matched targets to execute. But can be + // something else, depending on the meta-operation. + // + typedef vector action_targets; + + struct meta_operation_info + { + const meta_operation_id id; + const string name; + + // Name derivatives for diagnostics. If empty, then the meta- + // operation need not be mentioned. + // + const string name_do; // E.g., [to] 'configure'. + const string name_doing; // E.g., [while] 'configuring'. + const string name_did; // E.g., 'configured'. + const string name_done; // E.g., 'is configured'. + + // The first argument in all the callback is the meta-operation + // parameters. + // + // If the meta-operation expects parameters, then it should have a + // non-NULL meta_operation_pre(). Failed that, any parameters will be + // diagnosed as unexpected. + + // Start of meta-operation and operation batches. + // + // If operation_pre() is not NULL, then it may translate default_id + // (and only default_id) to some other operation. If not translated, + // then default_id is used. If, however, operation_pre() is NULL, + // then default_id is translated to update_id. + // + void (*meta_operation_pre) (const values&, const location&); + operation_id (*operation_pre) (const values&, operation_id); + + // Meta-operation-specific logic to load the buildfile, search and match + // the targets, and execute the action on the targets. + // + void (*load) (const values&, + scope& root, + const path& buildfile, + const dir_path& out_base, + const dir_path& src_base, + const location&); + + void (*search) (const values&, + const scope& root, + const scope& base, + const target_key&, + const location&, + action_targets&); + + void (*match) (const values&, action, action_targets&); + + void (*execute) (const values&, action, action_targets&, bool quiet); + + // Start of operation and meta-operation batches. + // + void (*operation_post) (const values&, operation_id); + void (*meta_operation_post) (const values&); + }; + + // Built-in meta-operations. + // + + // perform + // + + // Load the buildfile. This is the default implementation that first + // calls root_pre(), then creates the scope for out_base, and, finally, + // loads the buildfile unless it has already been loaded for the root + // scope. + // + void + load (const values&, + scope&, + const path&, + const dir_path&, + const dir_path&, + const location&); + + // Search and match the target. This is the default implementation + // that does just that and adds a pointer to the target to the list. + // + void + search (const values&, + const scope&, + const scope&, + const target_key&, + const location&, + action_targets&); + + void + match (const values&, action, action_targets&); + + // Execute the action on the list of targets. This is the default + // implementation that does just that while issuing appropriate + // diagnostics (unless quiet). + // + void + execute (const values&, action, const action_targets&, bool quiet); + + extern const meta_operation_info noop; + extern const meta_operation_info perform; + + // Operation info. + // + struct operation_info + { + const operation_id id; + const string name; + + // Name derivatives for diagnostics. Note that unlike meta-operations, + // these can only be empty for the default operation (id 1), And + // meta-operations that make use of the default operation shall not + // have empty derivatives (failed which only target name will be + // printed). + // + const string name_do; // E.g., [to] 'update'. + const string name_doing; // E.g., [while] 'updating'. + const string name_did; // E.g., [not] 'updated'. + const string name_done; // E.g., 'is up to date'. + + const execution_mode mode; + + // This is the operation's concurrency multiplier. 0 means run serially, + // 1 means run at hardware concurrency (unless overridden by the user). + // + const size_t concurrency; + + // The first argument in all the callback is the operation parameters. + // + // If the meta-operation expects parameters, then it should have a + // non-NULL pre(). Failed that, any parameters will be diagnosed as + // unexpected. + + // If the returned operation_id's are not 0, then they are injected + // as pre/post operations for this operation. Can be NULL if unused. + // The returned operation_id shall not be default_id. + // + operation_id (*pre) (const values&, meta_operation_id, const location&); + operation_id (*post) (const values&, meta_operation_id); + }; + + // Built-in operations. + // + extern const operation_info default_; + extern const operation_info update; + extern const operation_info clean; + + // Global meta/operation tables. Each registered meta/operation + // is assigned an id which is used as an index in the per-project + // registered meta/operation lists. + // + // We have three types of meta/operations: built-in (e.g., perform, + // update), pre-defined (e.g., configure, test), and dynamically- + // defined. For built-in ones, both the id and implementation are + // part of the build2 core. For pre-defined, the id is registered + // as part of the core but the implementation is loaded as part of + // a module. The idea with pre-defined operations is that they have + // common, well-established semantics but could still be optional. + // Another aspect of pre-defined operations is that often rules + // across multiple modules need to know their ids. Finally, + // dynamically-defined meta/operations have their ids registered + // as part of a module load. In this case, the meta/operation is + // normally (but not necessarily) fully implemented by this module. + // + // Note also that the name of a meta/operation in a sense defines + // its semantics. It would be strange to have an operation called + // test that does two very different things in different projects. + // + // A built-in/pre-defined meta-operation can also provide a pre-processor + // callback that will be called for operation-specs before any project + // discovery/bootstrap is performed. + // + struct meta_operation_data + { + // The processor may modify the parameters, opspec, and change the + // meta-operation by returning a different name. + // + // If lifted is true then the operation name in opspec is bogus (has + // been lifted) and the default/empty name should be assumed instead. + // + using process_func = const string& (const variable_overrides&, + values&, + vector_view&, + bool lifted, + const location&); + + meta_operation_data () = default; + meta_operation_data (const char* n, process_func p = nullptr) + : name (n), process (p) {} + + string name; + process_func* process; + }; + + extern butl::string_table meta_operation_table; + extern butl::string_table operation_table; + + // These are "sparse" in the sense that we may have "holes" that + // are represented as NULL pointers. Also, lookup out of bounds + // is treated as a hole. + // + template + struct sparse_vector + { + using base_type = vector; + using size_type = typename base_type::size_type; + + void + insert (size_type i, T& x) + { + size_type n (v_.size ()); + + if (i < n) + v_[i] = &x; + else + { + if (n != i) + v_.resize (i, nullptr); // Add holes. + v_.push_back (&x); + } + } + + T* + operator[] (size_type i) const + { + return i < v_.size () ? v_[i] : nullptr; + } + + bool + empty () const {return v_.empty ();} + + // Note that this is more of a "max index" rather than size. + // + size_type + size () const {return v_.size ();} + + private: + base_type v_; + }; + + using meta_operations = sparse_vector; + using operations = sparse_vector; +} + +namespace butl +{ + template <> + struct string_table_traits + { + static const std::string& + key (const build2::meta_operation_data& d) {return d.name;} + }; +} + +#endif // BUILD2_OPERATION_HXX diff --git a/build2/parser b/build2/parser deleted file mode 100644 index 16b7782..0000000 --- a/build2/parser +++ /dev/null @@ -1,593 +0,0 @@ -// file : build2/parser -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_PARSER -#define BUILD2_PARSER - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace build2 -{ - class scope; - class target; - - class parser - { - public: - // If boot is true, then we are parsing bootstrap.build and modules - // should only be bootstrapped. - // - parser (bool boot = false): fail ("error", &path_), boot_ (boot) {} - - // Issue diagnostics and throw failed in case of an error. - // - void - parse_buildfile (istream&, const path& name, scope& root, scope& base); - - buildspec - parse_buildspec (istream&, const path& name); - - token - parse_variable (lexer&, scope&, const variable&, token_type kind); - - pair - parse_variable_value (lexer&, scope&, const dir_path*, const variable&); - - names - parse_export_stub (istream& is, const path& p, scope& r, scope& b) - { - parse_buildfile (is, p, r, b); - return move (export_value_); - } - - // Recursive descent parser. - // - protected: - - // Pattern expansion mode. - // - enum class pattern_mode - { - ignore, // Treat as ordinary names. - detect, // Ignore pair/dir/type if the first name is a pattern. - expand // Expand to ordinary names. - }; - - // If one is true then parse a single (logical) line (logical means it - // can actually be several lines, e.g., an if-block). Return false if - // nothing has been parsed (i.e., we are on the same token). - // - // Note that after this function returns, the token is the first token on - // the next line (or eos). - // - bool - parse_clause (token&, token_type&, bool one = false); - - void - parse_assert (token&, token_type&); - - void - parse_print (token&, token_type&); - - void - parse_source (token&, token_type&); - - void - parse_include (token&, token_type&); - - void - parse_import (token&, token_type&); - - void - parse_export (token&, token_type&); - - void - parse_using (token&, token_type&); - - void - parse_define (token&, token_type&); - - void - parse_if_else (token&, token_type&); - - void - parse_variable (token&, token_type&, const variable&, token_type); - - string - parse_variable_name (names&&, const location&); - - // Note: calls attributes_push() that the caller must pop. - // - value - parse_variable_value (token&, token_type&); - - void - apply_variable_attributes (const variable&); - - void - apply_value_attributes (const variable*, // Optional. - value& lhs, - value&& rhs, - token_type assign_kind); - - // Return the value pack (values can be NULL/typed). Note that for an - // empty eval context ('()' potentially with whitespaces in between) the - // result is an empty pack, not a pack of one empty. - // - values - parse_eval (token&, token_type&, pattern_mode); - - values - parse_eval_comma (token&, token_type&, pattern_mode, bool = false); - - value - parse_eval_ternary (token&, token_type&, pattern_mode, bool = false); - - value - parse_eval_or (token&, token_type&, pattern_mode, bool = false); - - value - parse_eval_and (token&, token_type&, pattern_mode, bool = false); - - value - parse_eval_comp (token&, token_type&, pattern_mode, bool = false); - - value - parse_eval_value (token&, token_type&, pattern_mode, bool = false); - - // Attributes stack. We can have nested attributes, for example: - // - // x = [bool] ([uint64] $x == [uint64] $y) - // - // In this example we only apply the value attributes after evaluating - // the context, which has its own attributes. - // - struct attributes - { - bool has; // Has attributes flag. - location loc; // Start of attributes location. - vector> ats; // Attributes. - - explicit operator bool () const {return has;} - }; - - // Push a new entry into the attributes_ stack. If the next token is '[' - // parse the attribute sequence until ']' setting the 'has' flag and - // storing the result on the stack. Then get the next token and, if - // standalone is false, verify it is not newline/eos (i.e., there is - // something after it). Return the indication of whether there are - // any attributes and their location. - // - // Note that during pre-parsing nothing is pushed into the stack and - // the returned attributes object indicates there are no attributes. - // - pair - attributes_push (token&, token_type&, bool standalone = false); - - attributes - attributes_pop () - { - assert (!pre_parse_); - attributes r (move (attributes_.top ())); - attributes_.pop (); - return r; - } - - attributes& - attributes_top () {return attributes_.top ();} - - // If chunk is true, then parse the smallest but complete, name-wise, - // chunk of input. Note that in this case you may still end up with - // multiple names, for example, {foo bar} or $foo. In the pre-parse mode - // always return empty list of names. - // - // The what argument is used in diagnostics (e.g., "expected - // instead of ...". - // - // The separators argument specifies the special characters to recognize - // inside the name. These can be the directory separators and the '%' - // project separator. Note that even if it is NULL, the result may still - // contain non-simple names due to variable expansions. - // - - static const string name_separators; - - names - parse_names (token& t, token_type& tt, - pattern_mode pmode, - bool chunk = false, - const char* what = "name", - const string* separators = &name_separators) - { - names ns; - parse_names (t, tt, - ns, - pmode, - chunk, - what, - separators, - 0, - nullopt, nullptr, nullptr); - return ns; - } - - // As above but return the result as a value, which can be typed and NULL. - // - value - parse_value (token& t, token_type& tt, - pattern_mode pmode, - const char* what = "name", - const string* separators = &name_separators, - bool chunk = false) - { - names ns; - auto r (parse_names (t, tt, - ns, - pmode, - chunk, - what, - separators, - 0, - nullopt, nullptr, nullptr)); - - value v (r.type); // Potentially typed NULL value. - - // This should not fail since we are typing the result of reversal from - // the typed value. - // - if (r.not_null) - v.assign (move (ns), nullptr); - - return v; - } - - // Append names and return the indication if the parsed value is not NULL - // and whether it is typed (and whether it is a pattern if pattern_mode is - // detect). - // - // You may have noticed that what we return here is essentially a value - // and doing it this way (i.e., reversing it to untyped names and - // returning its type so that it can potentially be "typed back") is kind - // of backwards. The reason we are doing it this way is because in many - // places we expect things untyped and if we were to always return a - // (potentially typed) value, then we would have to reverse it in all - // those places. Still it may make sense to look into redesigning the - // whole thing one day. - // - // Currently the only way for the result to be NULL or have a type is if - // it is the result of a sole, unquoted variable expansion, function call, - // or context evaluation. - // - struct parse_names_result - { - bool not_null; - const value_type* type; - optional pattern; - }; - - parse_names_result - parse_names (token&, token_type&, - names&, - pattern_mode, - bool chunk = false, - const char* what = "name", - const string* separators = &name_separators, - size_t pairn = 0, - const optional& prj = nullopt, - const dir_path* dir = nullptr, - const string* type = nullptr, - bool cross = true); - - size_t - parse_names_trailer (token&, token_type&, - names&, - pattern_mode, - const char* what, - const string* separators, - size_t pairn, - const optional& prj, - const dir_path* dir, - const string* type, - bool cross); - - size_t - expand_name_pattern (const location&, - names&&, - names&, - const char* what, - size_t pairn, - const dir_path* dir, - const string* type, - const target_type*); - - size_t - splice_names (const location&, - const names_view&, - names&&, - names&, - const char* what, - size_t pairn, - const optional& prj, - const dir_path* dir, - const string* type); - - // Skip until newline or eos. - // - void - skip_line (token&, token_type&); - - // Skip until block-closing } or eos, taking into account nested blocks. - // - void - skip_block (token&, token_type&); - - // Return true if the name token can be considered a directive keyword. - // - bool - keyword (token&); - - // Buildspec. - // - buildspec - parse_buildspec_clause (token&, token_type&, size_t); - - // Customization hooks. - // - protected: - virtual lookup - lookup_variable (name&& qual, string&& name, const location&); - - // Utilities. - // - protected: - class enter_scope; - class enter_target; - - // Switch to a new current scope. Note that this function might also have - // to switch to a new root scope if the new current scope is in another - // project. So both must be saved and restored. - // - void - switch_scope (const dir_path&); - - void - process_default_target (token&); - - // Enter buildfile as a target. - // - void - enter_buildfile (const path&); - - // Lexer. - // - protected: - location - get_location (const token& t) const - { - return build2::get_location (t, *path_); - } - - token_type - next (token&, token_type&); - - // Be careful with peeking and switching the lexer mode. See keyword() - // for more information. - // - token_type - peek (); - - token_type - peek (lexer_mode m, char ps = '\0') - { - // The idea is that if we already have something peeked, then it should - // be in the same mode. We also don't re-set the mode since it may have - // expired after the first token. - // - if (peeked_) - { - assert (peek_.mode == m); - return peek_.token.type; - } - - mode (m, ps); - return peek (); - } - - const token& - peeked () const - { - assert (peeked_); - return peek_.token; - } - - void - mode (lexer_mode m, char ps = '\0') - { - if (replay_ != replay::play) - lexer_->mode (m, ps); - else - // As a sanity check, make sure the mode matches the next token. Note - // that we don't check the pair separator since it can be overriden by - // the lexer's mode() implementation. - // - assert (replay_i_ != replay_data_.size () && - replay_data_[replay_i_].mode == m); - } - - lexer_mode - mode () const - { - if (replay_ != replay::play) - return lexer_->mode (); - else - { - assert (replay_i_ != replay_data_.size ()); - return replay_data_[replay_i_].mode; - } - } - - void - expire_mode () - { - if (replay_ != replay::play) - lexer_->expire_mode (); - } - - // Token saving and replaying. Note that it can only be used in certain - // contexts. Specifically, the code that parses a replay must not interact - // with the lexer directly (e.g., the keyword() test). For now we don't - // enforce any of this. - // - // Note also that the peeked token is not part of the replay, until it - // is "got". - // - void - replay_save () - { - assert (replay_ == replay::stop); - replay_ = replay::save; - } - - void - replay_play () - { - assert ((replay_ == replay::save && !replay_data_.empty ()) || - (replay_ == replay::play && replay_i_ == replay_data_.size ())); - - if (replay_ == replay::save) - replay_path_ = path_; // Save old path. - - replay_i_ = 0; - replay_ = replay::play; - } - - void - replay_stop () - { - if (replay_ == replay::play) - path_ = replay_path_; // Restore old path. - - replay_data_.clear (); - replay_ = replay::stop; - } - - struct replay_guard - { - replay_guard (parser& p, bool start = true) - : p_ (start ? &p : nullptr) - { - if (p_ != nullptr) - p_->replay_save (); - } - - void - play () - { - if (p_ != nullptr) - p_->replay_play (); - } - - ~replay_guard () - { - if (p_ != nullptr) - p_->replay_stop (); - } - - private: - parser* p_; - }; - - // Stop saving and get the data. - // - replay_tokens - replay_data () - { - assert (replay_ == replay::save); - - replay_tokens r (move (replay_data_)); - replay_data_.clear (); - replay_ = replay::stop; - return r; - } - - // Set the data and start playing. - // - void - replay_data (replay_tokens&& d) - { - assert (replay_ == replay::stop); - - replay_path_ = path_; // Save old path. - - replay_data_ = move (d); - replay_i_ = 0; - replay_ = replay::play; - } - - // Implementation details, don't call directly. - // - replay_token - lexer_next () - { - lexer_mode m (lexer_->mode ()); // Get it first since it may expire. - return replay_token {lexer_->next (), path_, m}; - } - - const replay_token& - replay_next () - { - assert (replay_i_ != replay_data_.size ()); - const replay_token& rt (replay_data_[replay_i_++]); - - // Update the path. Note that theoretically it is possible that peeking - // at the next token will "change" the path of the current token. The - // workaround would be to call get_location() before peeking. - // - path_ = rt.file; - - return rt; - } - - // Diagnostics. - // - protected: - const fail_mark fail; - - protected: - bool pre_parse_ = false; - bool boot_; - - const path* path_; // Current path. - lexer* lexer_; - target* target_; // Current target, if any. - scope* scope_; // Current base scope (out_base). - scope* root_; // Current root scope (out_root). - - const dir_path* pbase_ = nullptr; // Current pattern base directory. - - std::stack attributes_; - - target* default_target_; - names export_value_; - - replay_token peek_; - bool peeked_ = false; - - enum class replay {stop, save, play} replay_ = replay::stop; - replay_tokens replay_data_; - size_t replay_i_; // Position of the next token during replay. - const path* replay_path_; // Path before replay began (to be restored). - }; -} - -#endif // BUILD2_PARSER diff --git a/build2/parser.cxx b/build2/parser.cxx index 9dbce3b..71e742c 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -2,21 +2,21 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // cout -#include // path_search(), path_match() - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // path_search(), path_match() + +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace std; diff --git a/build2/parser.hxx b/build2/parser.hxx new file mode 100644 index 0000000..85da4e5 --- /dev/null +++ b/build2/parser.hxx @@ -0,0 +1,593 @@ +// file : build2/parser.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PARSER_HXX +#define BUILD2_PARSER_HXX + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace build2 +{ + class scope; + class target; + + class parser + { + public: + // If boot is true, then we are parsing bootstrap.build and modules + // should only be bootstrapped. + // + parser (bool boot = false): fail ("error", &path_), boot_ (boot) {} + + // Issue diagnostics and throw failed in case of an error. + // + void + parse_buildfile (istream&, const path& name, scope& root, scope& base); + + buildspec + parse_buildspec (istream&, const path& name); + + token + parse_variable (lexer&, scope&, const variable&, token_type kind); + + pair + parse_variable_value (lexer&, scope&, const dir_path*, const variable&); + + names + parse_export_stub (istream& is, const path& p, scope& r, scope& b) + { + parse_buildfile (is, p, r, b); + return move (export_value_); + } + + // Recursive descent parser. + // + protected: + + // Pattern expansion mode. + // + enum class pattern_mode + { + ignore, // Treat as ordinary names. + detect, // Ignore pair/dir/type if the first name is a pattern. + expand // Expand to ordinary names. + }; + + // If one is true then parse a single (logical) line (logical means it + // can actually be several lines, e.g., an if-block). Return false if + // nothing has been parsed (i.e., we are on the same token). + // + // Note that after this function returns, the token is the first token on + // the next line (or eos). + // + bool + parse_clause (token&, token_type&, bool one = false); + + void + parse_assert (token&, token_type&); + + void + parse_print (token&, token_type&); + + void + parse_source (token&, token_type&); + + void + parse_include (token&, token_type&); + + void + parse_import (token&, token_type&); + + void + parse_export (token&, token_type&); + + void + parse_using (token&, token_type&); + + void + parse_define (token&, token_type&); + + void + parse_if_else (token&, token_type&); + + void + parse_variable (token&, token_type&, const variable&, token_type); + + string + parse_variable_name (names&&, const location&); + + // Note: calls attributes_push() that the caller must pop. + // + value + parse_variable_value (token&, token_type&); + + void + apply_variable_attributes (const variable&); + + void + apply_value_attributes (const variable*, // Optional. + value& lhs, + value&& rhs, + token_type assign_kind); + + // Return the value pack (values can be NULL/typed). Note that for an + // empty eval context ('()' potentially with whitespaces in between) the + // result is an empty pack, not a pack of one empty. + // + values + parse_eval (token&, token_type&, pattern_mode); + + values + parse_eval_comma (token&, token_type&, pattern_mode, bool = false); + + value + parse_eval_ternary (token&, token_type&, pattern_mode, bool = false); + + value + parse_eval_or (token&, token_type&, pattern_mode, bool = false); + + value + parse_eval_and (token&, token_type&, pattern_mode, bool = false); + + value + parse_eval_comp (token&, token_type&, pattern_mode, bool = false); + + value + parse_eval_value (token&, token_type&, pattern_mode, bool = false); + + // Attributes stack. We can have nested attributes, for example: + // + // x = [bool] ([uint64] $x == [uint64] $y) + // + // In this example we only apply the value attributes after evaluating + // the context, which has its own attributes. + // + struct attributes + { + bool has; // Has attributes flag. + location loc; // Start of attributes location. + vector> ats; // Attributes. + + explicit operator bool () const {return has;} + }; + + // Push a new entry into the attributes_ stack. If the next token is '[' + // parse the attribute sequence until ']' setting the 'has' flag and + // storing the result on the stack. Then get the next token and, if + // standalone is false, verify it is not newline/eos (i.e., there is + // something after it). Return the indication of whether there are + // any attributes and their location. + // + // Note that during pre-parsing nothing is pushed into the stack and + // the returned attributes object indicates there are no attributes. + // + pair + attributes_push (token&, token_type&, bool standalone = false); + + attributes + attributes_pop () + { + assert (!pre_parse_); + attributes r (move (attributes_.top ())); + attributes_.pop (); + return r; + } + + attributes& + attributes_top () {return attributes_.top ();} + + // If chunk is true, then parse the smallest but complete, name-wise, + // chunk of input. Note that in this case you may still end up with + // multiple names, for example, {foo bar} or $foo. In the pre-parse mode + // always return empty list of names. + // + // The what argument is used in diagnostics (e.g., "expected + // instead of ...". + // + // The separators argument specifies the special characters to recognize + // inside the name. These can be the directory separators and the '%' + // project separator. Note that even if it is NULL, the result may still + // contain non-simple names due to variable expansions. + // + + static const string name_separators; + + names + parse_names (token& t, token_type& tt, + pattern_mode pmode, + bool chunk = false, + const char* what = "name", + const string* separators = &name_separators) + { + names ns; + parse_names (t, tt, + ns, + pmode, + chunk, + what, + separators, + 0, + nullopt, nullptr, nullptr); + return ns; + } + + // As above but return the result as a value, which can be typed and NULL. + // + value + parse_value (token& t, token_type& tt, + pattern_mode pmode, + const char* what = "name", + const string* separators = &name_separators, + bool chunk = false) + { + names ns; + auto r (parse_names (t, tt, + ns, + pmode, + chunk, + what, + separators, + 0, + nullopt, nullptr, nullptr)); + + value v (r.type); // Potentially typed NULL value. + + // This should not fail since we are typing the result of reversal from + // the typed value. + // + if (r.not_null) + v.assign (move (ns), nullptr); + + return v; + } + + // Append names and return the indication if the parsed value is not NULL + // and whether it is typed (and whether it is a pattern if pattern_mode is + // detect). + // + // You may have noticed that what we return here is essentially a value + // and doing it this way (i.e., reversing it to untyped names and + // returning its type so that it can potentially be "typed back") is kind + // of backwards. The reason we are doing it this way is because in many + // places we expect things untyped and if we were to always return a + // (potentially typed) value, then we would have to reverse it in all + // those places. Still it may make sense to look into redesigning the + // whole thing one day. + // + // Currently the only way for the result to be NULL or have a type is if + // it is the result of a sole, unquoted variable expansion, function call, + // or context evaluation. + // + struct parse_names_result + { + bool not_null; + const value_type* type; + optional pattern; + }; + + parse_names_result + parse_names (token&, token_type&, + names&, + pattern_mode, + bool chunk = false, + const char* what = "name", + const string* separators = &name_separators, + size_t pairn = 0, + const optional& prj = nullopt, + const dir_path* dir = nullptr, + const string* type = nullptr, + bool cross = true); + + size_t + parse_names_trailer (token&, token_type&, + names&, + pattern_mode, + const char* what, + const string* separators, + size_t pairn, + const optional& prj, + const dir_path* dir, + const string* type, + bool cross); + + size_t + expand_name_pattern (const location&, + names&&, + names&, + const char* what, + size_t pairn, + const dir_path* dir, + const string* type, + const target_type*); + + size_t + splice_names (const location&, + const names_view&, + names&&, + names&, + const char* what, + size_t pairn, + const optional& prj, + const dir_path* dir, + const string* type); + + // Skip until newline or eos. + // + void + skip_line (token&, token_type&); + + // Skip until block-closing } or eos, taking into account nested blocks. + // + void + skip_block (token&, token_type&); + + // Return true if the name token can be considered a directive keyword. + // + bool + keyword (token&); + + // Buildspec. + // + buildspec + parse_buildspec_clause (token&, token_type&, size_t); + + // Customization hooks. + // + protected: + virtual lookup + lookup_variable (name&& qual, string&& name, const location&); + + // Utilities. + // + protected: + class enter_scope; + class enter_target; + + // Switch to a new current scope. Note that this function might also have + // to switch to a new root scope if the new current scope is in another + // project. So both must be saved and restored. + // + void + switch_scope (const dir_path&); + + void + process_default_target (token&); + + // Enter buildfile as a target. + // + void + enter_buildfile (const path&); + + // Lexer. + // + protected: + location + get_location (const token& t) const + { + return build2::get_location (t, *path_); + } + + token_type + next (token&, token_type&); + + // Be careful with peeking and switching the lexer mode. See keyword() + // for more information. + // + token_type + peek (); + + token_type + peek (lexer_mode m, char ps = '\0') + { + // The idea is that if we already have something peeked, then it should + // be in the same mode. We also don't re-set the mode since it may have + // expired after the first token. + // + if (peeked_) + { + assert (peek_.mode == m); + return peek_.token.type; + } + + mode (m, ps); + return peek (); + } + + const token& + peeked () const + { + assert (peeked_); + return peek_.token; + } + + void + mode (lexer_mode m, char ps = '\0') + { + if (replay_ != replay::play) + lexer_->mode (m, ps); + else + // As a sanity check, make sure the mode matches the next token. Note + // that we don't check the pair separator since it can be overriden by + // the lexer's mode() implementation. + // + assert (replay_i_ != replay_data_.size () && + replay_data_[replay_i_].mode == m); + } + + lexer_mode + mode () const + { + if (replay_ != replay::play) + return lexer_->mode (); + else + { + assert (replay_i_ != replay_data_.size ()); + return replay_data_[replay_i_].mode; + } + } + + void + expire_mode () + { + if (replay_ != replay::play) + lexer_->expire_mode (); + } + + // Token saving and replaying. Note that it can only be used in certain + // contexts. Specifically, the code that parses a replay must not interact + // with the lexer directly (e.g., the keyword() test). For now we don't + // enforce any of this. + // + // Note also that the peeked token is not part of the replay, until it + // is "got". + // + void + replay_save () + { + assert (replay_ == replay::stop); + replay_ = replay::save; + } + + void + replay_play () + { + assert ((replay_ == replay::save && !replay_data_.empty ()) || + (replay_ == replay::play && replay_i_ == replay_data_.size ())); + + if (replay_ == replay::save) + replay_path_ = path_; // Save old path. + + replay_i_ = 0; + replay_ = replay::play; + } + + void + replay_stop () + { + if (replay_ == replay::play) + path_ = replay_path_; // Restore old path. + + replay_data_.clear (); + replay_ = replay::stop; + } + + struct replay_guard + { + replay_guard (parser& p, bool start = true) + : p_ (start ? &p : nullptr) + { + if (p_ != nullptr) + p_->replay_save (); + } + + void + play () + { + if (p_ != nullptr) + p_->replay_play (); + } + + ~replay_guard () + { + if (p_ != nullptr) + p_->replay_stop (); + } + + private: + parser* p_; + }; + + // Stop saving and get the data. + // + replay_tokens + replay_data () + { + assert (replay_ == replay::save); + + replay_tokens r (move (replay_data_)); + replay_data_.clear (); + replay_ = replay::stop; + return r; + } + + // Set the data and start playing. + // + void + replay_data (replay_tokens&& d) + { + assert (replay_ == replay::stop); + + replay_path_ = path_; // Save old path. + + replay_data_ = move (d); + replay_i_ = 0; + replay_ = replay::play; + } + + // Implementation details, don't call directly. + // + replay_token + lexer_next () + { + lexer_mode m (lexer_->mode ()); // Get it first since it may expire. + return replay_token {lexer_->next (), path_, m}; + } + + const replay_token& + replay_next () + { + assert (replay_i_ != replay_data_.size ()); + const replay_token& rt (replay_data_[replay_i_++]); + + // Update the path. Note that theoretically it is possible that peeking + // at the next token will "change" the path of the current token. The + // workaround would be to call get_location() before peeking. + // + path_ = rt.file; + + return rt; + } + + // Diagnostics. + // + protected: + const fail_mark fail; + + protected: + bool pre_parse_ = false; + bool boot_; + + const path* path_; // Current path. + lexer* lexer_; + target* target_; // Current target, if any. + scope* scope_; // Current base scope (out_base). + scope* root_; // Current root scope (out_root). + + const dir_path* pbase_ = nullptr; // Current pattern base directory. + + std::stack attributes_; + + target* default_target_; + names export_value_; + + replay_token peek_; + bool peeked_ = false; + + enum class replay {stop, save, play} replay_ = replay::stop; + replay_tokens replay_data_; + size_t replay_i_; // Position of the next token during replay. + const path* replay_path_; // Path before replay began (to be restored). + }; +} + +#endif // BUILD2_PARSER_HXX diff --git a/build2/pkgconfig/init b/build2/pkgconfig/init deleted file mode 100644 index 5dac923..0000000 --- a/build2/pkgconfig/init +++ /dev/null @@ -1,37 +0,0 @@ -// file : build2/pkgconfig/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_PKGCONFIG_INIT -#define BUILD2_PKGCONFIG_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace pkgconfig - { - bool - config_init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_PKGCONFIG_INIT diff --git a/build2/pkgconfig/init.cxx b/build2/pkgconfig/init.cxx index 352e845..fa22421 100644 --- a/build2/pkgconfig/init.cxx +++ b/build2/pkgconfig/init.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include using namespace std; using namespace butl; diff --git a/build2/pkgconfig/init.hxx b/build2/pkgconfig/init.hxx new file mode 100644 index 0000000..eed224f --- /dev/null +++ b/build2/pkgconfig/init.hxx @@ -0,0 +1,37 @@ +// file : build2/pkgconfig/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PKGCONFIG_INIT_HXX +#define BUILD2_PKGCONFIG_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace pkgconfig + { + bool + config_init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_PKGCONFIG_INIT_HXX diff --git a/build2/prerequisite b/build2/prerequisite deleted file mode 100644 index e3b78a2..0000000 --- a/build2/prerequisite +++ /dev/null @@ -1,166 +0,0 @@ -// file : build2/prerequisite -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_PREREQUISITE -#define BUILD2_PREREQUISITE - -#include - -#include -#include - -#include -#include - -namespace build2 -{ - class scope; - class target; - - // Light-weight (by being shallow-pointing) prerequisite key, similar - // to (and based on) target key. - // - // Note that unlike prerequisite, the key is not (necessarily) owned by a - // target. So for the key we instead have the base scope of the target that - // (would) own it. Note that we assume keys to be ephemeral enough for the - // base scope to remain unchanged. - // - class prerequisite_key - { - public: - typedef build2::scope scope_type; - - const optional& proj; - target_key tk; // The .dir and .out members can be relative. - const scope_type* scope; // Can be NULL if tk.dir is absolute. - - static const optional nullproj; - - template - bool is_a () const {return tk.is_a ();} - bool is_a (const target_type& tt) const {return tk.is_a (tt);} - }; - - ostream& - operator<< (ostream&, const prerequisite_key&); - - // Note that every data member except for the target is immutable (const). - // - class prerequisite - { - public: - using scope_type = build2::scope; - using target_type = build2::target; - using target_type_type = build2::target_type; - - // Note that unlike targets, for prerequisites an empty out directory - // means undetermined rather than being definitely in the out tree. - // - // It might seem natural to keep the reference to the owner target instead - // of to the scope. But that's not the semantics that we have, consider: - // - // foo/obj{x}: bar/cxx{y} - // - // bar/ here is relative to the scope, not to foo/. Plus, bar/ can resolve - // to either src or out. - // - const optional proj; - const target_type_type& type; - const dir_path dir; // Normalized absolute or relative (to scope). - const dir_path out; // Empty, normalized absolute, or relative. - const string name; - const optional ext; // Absent if unspecified. - const scope_type& scope; - - // NULL if not yet resolved. Note that this should always be the "primary - // target", not a member of a target group. - // - // While normally only a matching rule should change this, if the - // prerequisite comes from the group, then it's possible that several - // rules will try to update it simultaneously. Thus the atomic. - // - mutable atomic target {nullptr}; - - public: - prerequisite (optional p, - const target_type_type& t, - dir_path d, - dir_path o, - string n, - optional e, - const scope_type& s) - : proj (move (p)), - type (t), - dir (move (d)), - out (move (o)), - name (move (n)), - ext (move (e)), - scope (s) {} - - // Make a prerequisite from a target. - // - explicit - prerequisite (const target_type&); - - // Note that the returned key "tracks" the prerequisite; that is, any - // updates to the prerequisite's members will be reflected in the key. - // - prerequisite_key - key () const - { - return prerequisite_key {proj, {&type, &dir, &out, &name, ext}, &scope}; - } - - // Return true if this prerequisite instance (physically) belongs to the - // target's prerequisite list. Note that this test only works if you use - // references to the container elements and the container hasn't been - // resized since such a reference was obtained. Normally this function is - // used when iterating over a combined prerequisites range to detect if - // the prerequisite came from the group (see group_prerequisites). - // - bool - belongs (const target_type&) const; - - // Prerequisite (target) type. - // - public: - template - bool - is_a () const {return type.is_a ();} - - bool - is_a (const target_type_type& tt) const {return type.is_a (tt);} - - public: - prerequisite (prerequisite&& x) - : proj (move (x.proj)), - type (move (x.type)), - dir (move (x.dir)), - out (move (x.out)), - name (move (x.name)), - ext (move (x.ext)), - scope (move (x.scope)), - target (x.target.load (memory_order_relaxed)) {} - - prerequisite (const prerequisite& x, memory_order o = memory_order_consume) - : proj (x.proj), - type (x.type), - dir (x.dir), - out (x.out), - name (x.name), - ext (x.ext), - scope (x.scope), - target (x.target.load (o)) {} - }; - - inline ostream& - operator<< (ostream& os, const prerequisite& p) - { - return os << p.key (); - } - - using prerequisites = vector; -} - -#endif // BUILD2_PREREQUISITE diff --git a/build2/prerequisite.cxx b/build2/prerequisite.cxx index b1f3cb2..7facbaa 100644 --- a/build2/prerequisite.cxx +++ b/build2/prerequisite.cxx @@ -2,12 +2,12 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include using namespace std; diff --git a/build2/prerequisite.hxx b/build2/prerequisite.hxx new file mode 100644 index 0000000..77d96bf --- /dev/null +++ b/build2/prerequisite.hxx @@ -0,0 +1,166 @@ +// file : build2/prerequisite.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_PREREQUISITE_HXX +#define BUILD2_PREREQUISITE_HXX + +#include + +#include +#include + +#include +#include + +namespace build2 +{ + class scope; + class target; + + // Light-weight (by being shallow-pointing) prerequisite key, similar + // to (and based on) target key. + // + // Note that unlike prerequisite, the key is not (necessarily) owned by a + // target. So for the key we instead have the base scope of the target that + // (would) own it. Note that we assume keys to be ephemeral enough for the + // base scope to remain unchanged. + // + class prerequisite_key + { + public: + typedef build2::scope scope_type; + + const optional& proj; + target_key tk; // The .dir and .out members can be relative. + const scope_type* scope; // Can be NULL if tk.dir is absolute. + + static const optional nullproj; + + template + bool is_a () const {return tk.is_a ();} + bool is_a (const target_type& tt) const {return tk.is_a (tt);} + }; + + ostream& + operator<< (ostream&, const prerequisite_key&); + + // Note that every data member except for the target is immutable (const). + // + class prerequisite + { + public: + using scope_type = build2::scope; + using target_type = build2::target; + using target_type_type = build2::target_type; + + // Note that unlike targets, for prerequisites an empty out directory + // means undetermined rather than being definitely in the out tree. + // + // It might seem natural to keep the reference to the owner target instead + // of to the scope. But that's not the semantics that we have, consider: + // + // foo/obj{x}: bar/cxx{y} + // + // bar/ here is relative to the scope, not to foo/. Plus, bar/ can resolve + // to either src or out. + // + const optional proj; + const target_type_type& type; + const dir_path dir; // Normalized absolute or relative (to scope). + const dir_path out; // Empty, normalized absolute, or relative. + const string name; + const optional ext; // Absent if unspecified. + const scope_type& scope; + + // NULL if not yet resolved. Note that this should always be the "primary + // target", not a member of a target group. + // + // While normally only a matching rule should change this, if the + // prerequisite comes from the group, then it's possible that several + // rules will try to update it simultaneously. Thus the atomic. + // + mutable atomic target {nullptr}; + + public: + prerequisite (optional p, + const target_type_type& t, + dir_path d, + dir_path o, + string n, + optional e, + const scope_type& s) + : proj (move (p)), + type (t), + dir (move (d)), + out (move (o)), + name (move (n)), + ext (move (e)), + scope (s) {} + + // Make a prerequisite from a target. + // + explicit + prerequisite (const target_type&); + + // Note that the returned key "tracks" the prerequisite; that is, any + // updates to the prerequisite's members will be reflected in the key. + // + prerequisite_key + key () const + { + return prerequisite_key {proj, {&type, &dir, &out, &name, ext}, &scope}; + } + + // Return true if this prerequisite instance (physically) belongs to the + // target's prerequisite list. Note that this test only works if you use + // references to the container elements and the container hasn't been + // resized since such a reference was obtained. Normally this function is + // used when iterating over a combined prerequisites range to detect if + // the prerequisite came from the group (see group_prerequisites). + // + bool + belongs (const target_type&) const; + + // Prerequisite (target) type. + // + public: + template + bool + is_a () const {return type.is_a ();} + + bool + is_a (const target_type_type& tt) const {return type.is_a (tt);} + + public: + prerequisite (prerequisite&& x) + : proj (move (x.proj)), + type (move (x.type)), + dir (move (x.dir)), + out (move (x.out)), + name (move (x.name)), + ext (move (x.ext)), + scope (move (x.scope)), + target (x.target.load (memory_order_relaxed)) {} + + prerequisite (const prerequisite& x, memory_order o = memory_order_consume) + : proj (x.proj), + type (x.type), + dir (x.dir), + out (x.out), + name (x.name), + ext (x.ext), + scope (x.scope), + target (x.target.load (o)) {} + }; + + inline ostream& + operator<< (ostream& os, const prerequisite& p) + { + return os << p.key (); + } + + using prerequisites = vector; +} + +#endif // BUILD2_PREREQUISITE_HXX diff --git a/build2/regex b/build2/regex deleted file mode 100644 index dc6dc96..0000000 --- a/build2/regex +++ /dev/null @@ -1,57 +0,0 @@ -// file : build2/regex -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_REGEX -#define BUILD2_REGEX - -#include -#include -#include // basic_string - -#include -#include - -namespace build2 -{ - // Like std::regex_match() but extends the standard ECMA-262 - // substitution escape sequences with a subset of Perl sequences: - // - // \\, \u, \l, \U, \L, \E, \1, ..., \9 - // - // Also return the resulting string as well as whether the search - // succeeded. - // - // Notes and limitations: - // - // - The only valid regex_constants flags are match_default, - // format_first_only (format_no_copy can easily be supported). - // - // - If backslash doesn't start any of the listed sequences then it is - // silently dropped and the following character is copied as is. - // - // - The character case conversion is performed according to the global - // C++ locale (which is, unless changed, is the same as C locale and - // both default to the POSIX locale aka "C"). - // - template - pair, bool> - regex_replace_ex (const std::basic_string&, - const std::basic_regex&, - const std::basic_string& fmt, - std::regex_constants::match_flag_type = - std::regex_constants::match_default); -} - -namespace std -{ - // Print regex error description but only if it is meaningful (this is also - // why we have to print leading colon). - // - ostream& - operator<< (ostream&, const regex_error&); -} - -#include - -#endif // BUILD2_REGEX diff --git a/build2/regex.cxx b/build2/regex.cxx index 40347b5..d96b860 100644 --- a/build2/regex.cxx +++ b/build2/regex.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #if defined(_MSC_VER) && _MSC_VER <= 1910 # include // strstr() diff --git a/build2/regex.hxx b/build2/regex.hxx new file mode 100644 index 0000000..1fa261b --- /dev/null +++ b/build2/regex.hxx @@ -0,0 +1,57 @@ +// file : build2/regex.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_REGEX_HXX +#define BUILD2_REGEX_HXX + +#include +#include +#include // basic_string + +#include +#include + +namespace build2 +{ + // Like std::regex_match() but extends the standard ECMA-262 + // substitution escape sequences with a subset of Perl sequences: + // + // \\, \u, \l, \U, \L, \E, \1, ..., \9 + // + // Also return the resulting string as well as whether the search + // succeeded. + // + // Notes and limitations: + // + // - The only valid regex_constants flags are match_default, + // format_first_only (format_no_copy can easily be supported). + // + // - If backslash doesn't start any of the listed sequences then it is + // silently dropped and the following character is copied as is. + // + // - The character case conversion is performed according to the global + // C++ locale (which is, unless changed, is the same as C locale and + // both default to the POSIX locale aka "C"). + // + template + pair, bool> + regex_replace_ex (const std::basic_string&, + const std::basic_regex&, + const std::basic_string& fmt, + std::regex_constants::match_flag_type = + std::regex_constants::match_default); +} + +namespace std +{ + // Print regex error description but only if it is meaningful (this is also + // why we have to print leading colon). + // + ostream& + operator<< (ostream&, const regex_error&); +} + +#include + +#endif // BUILD2_REGEX_HXX diff --git a/build2/rule b/build2/rule deleted file mode 100644 index d434d0d..0000000 --- a/build2/rule +++ /dev/null @@ -1,140 +0,0 @@ -// file : build2/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_RULE -#define BUILD2_RULE - -#include -#include - -#include -#include - -namespace build2 -{ - class match_result - { - public: - bool result; - - // If set, then this is a recipe's action. It must override the original - // action. Normally it is "unconditional inner operation". Only - // noop_recipe can be overridden. - // - // It is passed to rule::apply() so that prerequisites are matched for - // this action. It is also passed to target::recipe() so that if someone - // is matching this target for this action, we won't end-up re-matching - // it. However, the recipe itself is executed with the original action - // so that it can adjust its logic, if necessary. - // - action recipe_action = action (); - - explicit - operator bool () const {return result;} - - // Note that the from-bool constructor is intentionally implicit so that - // we can return true/false from match(). - // - match_result (bool r): result (r) {} - match_result (bool r, action a): result (r), recipe_action (a) {} - }; - - // Once a rule is registered (for a scope), it is treated as immutable. If - // you need to modify some state (e.g., counters or some such), then make - // sure it is MT-safe. - // - // Note that match() may not be followed by apply() or be called several - // times before the following apply() (see resolve_group_members()) which - // means that it should be idempotent. The target_data object in the call - // to match() may not be the same as target. - // - // match() can also be called by another rules (see cc/install). - // - class rule - { - public: - virtual match_result - match (action, target&, const string& hint) const = 0; - - virtual recipe - apply (action, target&) const = 0; - }; - - // Fallback rule that only matches if the file exists. - // - class file_rule: public rule - { - public: - file_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static const file_rule instance; - }; - - class alias_rule: public rule - { - public: - alias_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static const alias_rule instance; - }; - - class fsdir_rule: public rule - { - public: - fsdir_rule () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static target_state - perform_update (action, const target&); - - static target_state - perform_clean (action, const target&); - - // Sometimes, as an optimization, we want to emulate execute_direct() - // of fsdir{} without the overhead of switching to the execute phase. - // - static void - perform_update_direct (action, const target&); - - static const fsdir_rule instance; - }; - - // Fallback rule that always matches and does nothing. - // - class fallback_rule: public build2::rule - { - public: - fallback_rule () {} - - virtual match_result - match (action, target&, const string&) const override - { - return true; - } - - virtual recipe - apply (action, target&) const override {return noop_recipe;} - - static const fallback_rule instance; - }; -} - -#endif // BUILD2_RULE diff --git a/build2/rule-map b/build2/rule-map deleted file mode 100644 index 9fb4056..0000000 --- a/build2/rule-map +++ /dev/null @@ -1,121 +0,0 @@ -// file : build2/rule-map -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_RULE_MAP -#define BUILD2_RULE_MAP - -#include - -#include - -#include -#include - -#include - -namespace build2 -{ - class rule; - - using hint_rule_map = - butl::prefix_map, '.'>; - - using target_type_rule_map = std::map; - - // This is an "indexed map" with operation_id being the index. Entry - // with id 0 is a wildcard. - // - // Note that while we may resize some vectors during non-serial load, this - // is MT-safe since we never cache any references to their elements. - // - class operation_rule_map - { - public: - template - void - insert (operation_id oid, const char* hint, const rule& r) - { - // 3 is the number of builtin operations. - // - if (oid >= map_.size ()) - map_.resize ((oid < 3 ? 3 : oid) + 1); - - map_[oid][&T::static_type].emplace (hint, r); - } - - // Return NULL if not found. - // - const target_type_rule_map* - operator[] (operation_id oid) const - { - return map_.size () > oid ? &map_[oid] : nullptr; - } - - bool - empty () const {return map_.empty ();} - - private: - vector map_; - }; - - // This is another indexed map but this time meta_operation_id is the - // index. The implementation is different, however: here we use a linked - // list with the first, statically-allocated node corresponding to the - // perform meta-operation. The idea is to try and get away with a dynamic - // allocation for the common cases since most rules will be registered - // for perform, at least on non-root scopes. - // - // @@ Redo using small_vector? - // - class rule_map - { - public: - - template - void - insert (action_id a, const char* hint, const rule& r) - { - insert (a >> 4, a & 0x0F, hint, r); - } - - template - void - insert (meta_operation_id mid, - operation_id oid, - const char* hint, - const rule& r) - { - if (mid_ == mid) - map_.insert (oid, hint, r); - else - { - if (next_ == nullptr) - next_.reset (new rule_map (mid)); - - next_->insert (mid, oid, hint, r); - } - } - - // Return NULL if not found. - // - const operation_rule_map* - operator[] (meta_operation_id mid) const - { - return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid]; - } - - explicit - rule_map (meta_operation_id mid = perform_id): mid_ (mid) {} - - bool - empty () const {return map_.empty () && next_ == nullptr;} - - private: - meta_operation_id mid_; - operation_rule_map map_; - unique_ptr next_; - }; -} - -#endif // BUILD2_RULE_MAP diff --git a/build2/rule-map.hxx b/build2/rule-map.hxx new file mode 100644 index 0000000..4afe3d4 --- /dev/null +++ b/build2/rule-map.hxx @@ -0,0 +1,121 @@ +// file : build2/rule-map.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_RULE_MAP_HXX +#define BUILD2_RULE_MAP_HXX + +#include + +#include + +#include +#include + +#include + +namespace build2 +{ + class rule; + + using hint_rule_map = + butl::prefix_map, '.'>; + + using target_type_rule_map = std::map; + + // This is an "indexed map" with operation_id being the index. Entry + // with id 0 is a wildcard. + // + // Note that while we may resize some vectors during non-serial load, this + // is MT-safe since we never cache any references to their elements. + // + class operation_rule_map + { + public: + template + void + insert (operation_id oid, const char* hint, const rule& r) + { + // 3 is the number of builtin operations. + // + if (oid >= map_.size ()) + map_.resize ((oid < 3 ? 3 : oid) + 1); + + map_[oid][&T::static_type].emplace (hint, r); + } + + // Return NULL if not found. + // + const target_type_rule_map* + operator[] (operation_id oid) const + { + return map_.size () > oid ? &map_[oid] : nullptr; + } + + bool + empty () const {return map_.empty ();} + + private: + vector map_; + }; + + // This is another indexed map but this time meta_operation_id is the + // index. The implementation is different, however: here we use a linked + // list with the first, statically-allocated node corresponding to the + // perform meta-operation. The idea is to try and get away with a dynamic + // allocation for the common cases since most rules will be registered + // for perform, at least on non-root scopes. + // + // @@ Redo using small_vector? + // + class rule_map + { + public: + + template + void + insert (action_id a, const char* hint, const rule& r) + { + insert (a >> 4, a & 0x0F, hint, r); + } + + template + void + insert (meta_operation_id mid, + operation_id oid, + const char* hint, + const rule& r) + { + if (mid_ == mid) + map_.insert (oid, hint, r); + else + { + if (next_ == nullptr) + next_.reset (new rule_map (mid)); + + next_->insert (mid, oid, hint, r); + } + } + + // Return NULL if not found. + // + const operation_rule_map* + operator[] (meta_operation_id mid) const + { + return mid == mid_ ? &map_ : next_ == nullptr ? nullptr : (*next_)[mid]; + } + + explicit + rule_map (meta_operation_id mid = perform_id): mid_ (mid) {} + + bool + empty () const {return map_.empty () && next_ == nullptr;} + + private: + meta_operation_id mid_; + operation_rule_map map_; + unique_ptr next_; + }; +} + +#endif // BUILD2_RULE_MAP_HXX diff --git a/build2/rule.cxx b/build2/rule.cxx index 53f723b..a462e58 100644 --- a/build2/rule.cxx +++ b/build2/rule.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/rule.hxx b/build2/rule.hxx new file mode 100644 index 0000000..f6e23f6 --- /dev/null +++ b/build2/rule.hxx @@ -0,0 +1,140 @@ +// file : build2/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_RULE_HXX +#define BUILD2_RULE_HXX + +#include +#include + +#include +#include + +namespace build2 +{ + class match_result + { + public: + bool result; + + // If set, then this is a recipe's action. It must override the original + // action. Normally it is "unconditional inner operation". Only + // noop_recipe can be overridden. + // + // It is passed to rule::apply() so that prerequisites are matched for + // this action. It is also passed to target::recipe() so that if someone + // is matching this target for this action, we won't end-up re-matching + // it. However, the recipe itself is executed with the original action + // so that it can adjust its logic, if necessary. + // + action recipe_action = action (); + + explicit + operator bool () const {return result;} + + // Note that the from-bool constructor is intentionally implicit so that + // we can return true/false from match(). + // + match_result (bool r): result (r) {} + match_result (bool r, action a): result (r), recipe_action (a) {} + }; + + // Once a rule is registered (for a scope), it is treated as immutable. If + // you need to modify some state (e.g., counters or some such), then make + // sure it is MT-safe. + // + // Note that match() may not be followed by apply() or be called several + // times before the following apply() (see resolve_group_members()) which + // means that it should be idempotent. The target_data object in the call + // to match() may not be the same as target. + // + // match() can also be called by another rules (see cc/install). + // + class rule + { + public: + virtual match_result + match (action, target&, const string& hint) const = 0; + + virtual recipe + apply (action, target&) const = 0; + }; + + // Fallback rule that only matches if the file exists. + // + class file_rule: public rule + { + public: + file_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static const file_rule instance; + }; + + class alias_rule: public rule + { + public: + alias_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static const alias_rule instance; + }; + + class fsdir_rule: public rule + { + public: + fsdir_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + + static target_state + perform_clean (action, const target&); + + // Sometimes, as an optimization, we want to emulate execute_direct() + // of fsdir{} without the overhead of switching to the execute phase. + // + static void + perform_update_direct (action, const target&); + + static const fsdir_rule instance; + }; + + // Fallback rule that always matches and does nothing. + // + class fallback_rule: public build2::rule + { + public: + fallback_rule () {} + + virtual match_result + match (action, target&, const string&) const override + { + return true; + } + + virtual recipe + apply (action, target&) const override {return noop_recipe;} + + static const fallback_rule instance; + }; +} + +#endif // BUILD2_RULE_HXX diff --git a/build2/scheduler b/build2/scheduler deleted file mode 100644 index c6f4f7f..0000000 --- a/build2/scheduler +++ /dev/null @@ -1,593 +0,0 @@ -// file : build2/scheduler -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_SCHEDULER -#define BUILD2_SCHEDULER - -#include -#include -#include -#include -#include // aligned_storage, etc -#include - -#include -#include - -namespace build2 -{ - // Scheduler of tasks and threads. Works best for "substantial" tasks (e.g., - // running a process), where in comparison thread synchronization overhead - // is negligible. - // - // A thread (called "master") may need to perform several tasks which can be - // done in parallel (e.g., update all the prerequisites or run all the - // tests). To acomplish this, the master, via a call to async(), can ask the - // scheduler to run a task in another thread (called "helper"). If a helper - // is available, then the task is executed asynchronously by such helper. - // Otherwise, the task is (normally) executed synchronously as part of the - // wait() call below. However, in certain cases (serial execution or full - // queue), the task may be executed synchronously as part of the async() - // call itself. Once the master thread has scheduled all the tasks, it calls - // wait() to await for their completion. - // - // The scheduler makes sure that only a certain number of threads (for - // example, the number of available hardware threads) are "active" at any - // given time. When a master thread calls wait(), it is "suspended" until - // all its asynchronous tasks are completed (at which point it becomes - // "ready"). A suspension of a master results in either another ready master - // being "resumed" or another helper thread becoming available. - // - // On completion of a task a helper thread returns to the scheduler which - // can again lead either to a ready master being resumed (in which case the - // helper is suspended) or the helper becoming available to perform another - // task. - // - // Note that suspended threads are not reused as helpers. Rather, a new - // helper thread is always created if none is available. This is done to - // allow a ready master to continue as soon as possible. If it were reused - // as a helper, then it could be blocked on a nested wait() further down the - // stack. All this means that the number of threads created by the scheduler - // will normally exceed the maximum active allowed. - // - class scheduler - { - public: - using atomic_count = std::atomic; - - // F should return void and not throw any exceptions. The way the result - // of a task is communicated back to the master thread is ad hoc, usually - // via "out" arguments. Such result(s) can only be retrieved by the master - // once its task count reaches the start count. - // - // The argument passing semantics is the same as for std::thread. In - // particular, lvalue-references are passed as copies (use ref()/cref() - // for the by-reference semantics), except the case where the task is - // executed synchronously and as part of the async() call itself (this - // subtlety can become important when passing shared locks; you would - // only want it to be copied if the task is queued). - // - // Return true if the task was queued and false if it was executed - // synchronously. - // - // If the scheduler is shutdown, throw system_error(ECANCELED). - // - template - bool - async (size_t start_count, atomic_count& task_count, F&&, A&&...); - - template - bool - async (atomic_count& task_count, F&& f, A&&... a) - { - return async (0, task_count, forward (f), forward (a)...); - } - - // Wait until the task count reaches the start count or less. If the - // scheduler is shutdown while waiting, throw system_error(ECANCELED). - // Return the value of task count. Note that this is a synchronizaiton - // point (i.e., the task count is checked with memory_order_acquire). - // - // Note that it is valid to wait on another thread's task count (that is, - // without making any async() calls in this thread). However, if the start - // count differs from the one passed to async(), then whomever sets the - // start count to this alternative value must also call resume() below - // in order to signal waiting threads. - // - // Note also that in this case (waiting on someone else's start count), - // the async() call could execute the tasks synchronously without ever - // incrementing the task count. Thus if waiting on another thread's start - // count starts before/during async() calls, then it must be "gated" with - // an alternative (lower) start count. - // - // Finally, if waiting on someone else's start count, it may be unsafe - // (from the deadlock's point of view) to continue working through our own - // queue (i.e., we may block waiting on a task that has been queued before - // us which in turn may end up waiting on "us"). - // - enum work_queue - { - work_none, // Don't work own queue. - work_one, // Work own queue rechecking the task count after every task. - work_all // Work own queue before rechecking the task count. - }; - - size_t - wait (size_t start_count, - const atomic_count& task_count, - work_queue = work_all); - - size_t - wait (const atomic_count& task_count, work_queue wq = work_all) - { - return wait (0, task_count, wq); - } - - // Resume threads waiting on this task count. - // - void - resume (const atomic_count& task_count); - - // An active thread that is about to wait for potentially significant time - // on something other than task_count (e.g., mutex, condition variable) - // should deactivate itself with the scheduler and then reactivate once - // done waiting. - // - void - deactivate (); - - void - activate (bool collision = false); - - // Startup and shutdown. - // - public: - // Unless already shut down, call shutdown() but ignore errors. - // - ~scheduler (); - - // Create a shut down scheduler. - // - scheduler () = default; - - // Create a started up scheduler. - // - // The initial active argument is the number of threads to assume are - // already active (e.g., the calling thread). It must not be 0 (since - // someone has to schedule the first task). - // - // If the maximum threads or task queue depth arguments are unspecified, - // then appropriate defaults are used. - // - scheduler (size_t max_active, - size_t init_active = 1, - size_t max_threads = 0, - size_t queue_depth = 0) - { - startup (max_active, init_active, max_threads, queue_depth); - } - - // Start the scheduler. - // - void - startup (size_t max_active, - size_t init_active = 1, - size_t max_threads = 0, - size_t queue_depth = 0); - - // Return true if the scheduler was started up. - // - // Note: can only be called from threads that have observed creation, - // startup, or shutdown. - // - bool - started () const {return !shutdown_;} - - // Tune a started up scheduler. - // - // Currently one cannot increase the number of max_active. Pass 0 to - // restore the initial value. - // - // Note that tuning can only be done while the scheduler is inactive, that - // is, no threads are executing a task or are suspended. For example, in a - // setup with a single initial active thread that would be after a return - // from the top-level wait() call. - // - void - tune (size_t max_active); - - // Return true if the scheduler is running serial. - // - // Note: can only be called from threads that have observed startup. - // - bool - serial () const {return max_active_ == 1;} - - // Wait for all the helper threads to terminate. Throw system_error on - // failure. Note that the initially active threads are not waited for. - // Return scheduling statistics. - // - struct stat - { - size_t thread_max_active = 0; // max # of active threads allowed. - size_t thread_max_total = 0; // max # of total threads allowed. - size_t thread_helpers = 0; // # of helper threads created. - size_t thread_max_waiting = 0; // max # of waiters at any time. - - size_t task_queue_depth = 0; // # of entries in a queue (capacity). - size_t task_queue_full = 0; // # of times task queue was full. - size_t task_queue_remain = 0; // # of tasks remaining in queue. - - size_t wait_queue_slots = 0; // # of wait slots (buckets). - size_t wait_queue_collisions = 0; // # of times slot had been occupied. - }; - - stat - shutdown (); - - // If initially active thread(s) (besides the one that calls startup()) - // exist before the call to startup(), then they must call join() before - // executing any tasks. The two common cases where you don't have to call - // join are a single active thread that calls startup()/shutdown() or - // active thread(s) that are created after startup(). - // - void - join () - { - assert (task_queue_ = nullptr); - - // Lock the mutex to make sure the values set in startup() are visible - // in this thread. - // - lock l (mutex_); - } - - // If initially active thread(s) participate in multiple schedulers and/or - // sessions (intervals between startup() and shutdown()), then they must - // call leave() before joining another scheduler/session. Note that this - // applies to the active thread that calls shutdown(). Note that a thread - // can only participate in one scheduler at a time. - // - void - leave () - { - task_queue_ = nullptr; - } - - // Return the number of hardware threads or 0 if unable to determine. - // - static size_t - hardware_concurrency () - { - return std::thread::hardware_concurrency (); - } - - // Return a prime number that can be used as a lock shard size that's - // appropriate for the scheduler's concurrency. Use power of two values - // for mul for higher-contention shards and for div for lower-contention - // ones. Always return 1 for serial execution. - // - // Note: can only be called from threads that have observed startup. - // - size_t - shard_size (size_t mul = 1, size_t div = 1) const; - - private: - using lock = std::unique_lock; - - void - activate_helper (lock&); - - void - create_helper (lock&); - - // We restrict ourselves to a single pointer as an argument in hope of - // a small object optimization. - // - static void - helper (void*); - - size_t - suspend (size_t start_count, const atomic_count& task_count); - - // Task encapsulation. - // - template - struct task_type - { - using func_type = std::decay_t; - using args_type = std::tuple...>; - - atomic_count* task_count; - size_t start_count; - func_type func; - args_type args; - - template - void - thunk (std::index_sequence) noexcept - { - move (func) (std::get (move (args))...); - } - }; - - template - static void - task_thunk (scheduler&, lock&, void*); - - template - static std::decay_t - decay_copy (T&& x) {return forward (x);} - - private: - std::mutex mutex_; - - bool shutdown_ = true; // Shutdown flag. - - // The constraints that we must maintain: - // - // active <= max_active - // (init_active + helpers) <= max_threads (soft; see activate_helper()) - // - // Note that the first three are immutable between startup() and - // shutdown() so can be accessed without a lock (but see join()). - // - size_t init_active_ = 0; // Initially active threads. - size_t max_active_ = 0; // Maximum number of active threads. - size_t max_threads_ = 0; // Maximum number of total threads. - - size_t helpers_ = 0; // Number of helper threads created so far. - - // Every thread that we manage must be accounted for in one of these - // counters. And their sum should equal (init_active + helpers). - // - size_t active_ = 0; // Active master threads executing a task. - size_t idle_ = 0; // Idle helper threads waiting for a task. - size_t waiting_ = 0; // Suspended master threads waiting for their tasks. - size_t ready_ = 0; // Ready master thread waiting to become active. - size_t starting_ = 0; // Helper threads starting up. - - // Original values (as specified during startup) that can be altered via - // tuning. - // - size_t orig_max_active_ = 0; - - std::condition_variable idle_condv_; // Idle helpers queue. - std::condition_variable ready_condv_; // Ready masters queue. - - // Statistics counters. - // - size_t stat_max_waiters_; - size_t stat_wait_collisions_; - - // Wait queue. - // - // A wait slot blocks a bunch of threads. When they are (all) unblocked, - // they re-examine their respective conditions and either carry on or - // block again. - // - // The wait queue is a shard of slots. A thread picks a slot based on the - // address of its task count variable. How many slots do we need? This - // depends on the number of waiters that we can have which cannot be - // greater than the total number of threads. - // - // The pointer to the task count is used to identify the already waiting - // group of threads for collision statistics. - // - struct wait_slot - { - std::mutex mutex; - std::condition_variable condv; - size_t waiters = 0; - const atomic_count* task_count; - bool shutdown = true; - }; - - size_t wait_queue_size_; // Proportional to max_threads. - unique_ptr wait_queue_; - - // Task queue. - // - // Each queue has its own mutex plus we have an atomic total count of the - // queued tasks. Note that it should only be modified while holding one - // of the queue lock. - // - atomic_count queued_task_count_; - - // For now we only support trivially-destructible tasks. - // - struct task_data - { - std::aligned_storage::type data; - void (*thunk) (scheduler&, lock&, void*); - }; - - // We have two requirements: Firstly, we want to keep the master thread - // (the one that called wait()) busy working though its own queue for as - // long as possible before (if at all) it "reincarnates" as a helper. The - // main reason for this is the limited number of helpers we can create. - // - // Secondly, we don't want to block wait() longer than necessary since the - // master thread can do some work with the result. Plus, overall, we want - // to "unwind" task hierarchies as soon as possible since they hold up - // resources such as thread's stack. All this means that the master thread - // can only work through tasks that it has queued at this "level" of the - // async()/wait() calls since we know that wait() cannot return until - // they are done. - // - // To satisfy the first requirement, the master and helper threads get the - // tasks from different ends of the queue: master from the back while - // helpers from the front. And the master always adds new tasks to the - // back. - // - // To satisfy the second requirement, the master thread stores the index - // of the first task it has queued at this "level" and makes sure it - // doesn't try to deque any task beyond that. - // - size_t task_queue_depth_; // Multiple of max_active. - - struct task_queue - { - std::mutex mutex; - bool shutdown = false; - - size_t stat_full = 0; // Number of times push() returned NULL. - - // Our task queue is circular with head being the index of the first - // element and tail -- of the last. Since this makes the empty and one - // element cases indistinguishable, we also keep the size. - // - // The mark is an index somewhere between (figuratively speaking) head - // and tail, if enabled. If the mark is hit, then it is disabled until - // the queue becomes empty or it is reset by a push. - // - size_t head = 0; - size_t mark = 0; - size_t tail = 0; - size_t size = 0; - - unique_ptr data; - - task_queue (size_t depth): data (new task_data[depth]) {} - }; - - // Task queue API. Expects the queue mutex to be locked. - // - - // Push a new task to the queue returning a pointer to the task data to be - // filled or NULL if the queue is full. - // - task_data* - push (task_queue& tq) - { - size_t& s (tq.size); - size_t& t (tq.tail); - size_t& m (tq.mark); - - if (s != task_queue_depth_) - { - // normal wrap empty - // | | | - t = s != 0 ? (t != task_queue_depth_ - 1 ? t + 1 : 0) : t; - s++; - - if (m == task_queue_depth_) // Enable the mark if first push. - m = t; - - queued_task_count_.fetch_add (1, std::memory_order_release); - return &tq.data[t]; - } - - return nullptr; - } - - bool - empty_front (task_queue& tq) const {return tq.size == 0;} - - void - pop_front (task_queue& tq, lock& ql) - { - size_t& s (tq.size); - size_t& h (tq.head); - size_t& m (tq.mark); - - bool a (h == m); // Adjust mark? - task_data& td (tq.data[h]); - - // normal wrap empty - // | | | - h = s != 1 ? (h != task_queue_depth_ - 1 ? h + 1 : 0) : h; - - if (--s == 0 || a) - m = h; // Reset or adjust the mark. - - queued_task_count_.fetch_sub (1, std::memory_order_release); - - // The thunk moves the task data to its stack, releases the lock, - // and continues to execute the task. - // - td.thunk (*this, ql, &td.data); - ql.lock (); - } - - bool - empty_back (task_queue& tq) const - { - return tq.size == 0 || tq.mark == task_queue_depth_; - } - - void - pop_back (task_queue& tq, lock& ql) - { - size_t& s (tq.size); - size_t& t (tq.tail); - size_t& m (tq.mark); - - bool a (t == m); // Adjust mark? - - task_data& td (tq.data[t]); - - // Save the old queue mark and disable it in case the task we are about - // to run adds sub-tasks. The first push(), if any, will reset it. - // - size_t om (m); - m = task_queue_depth_; - - // normal wrap empty - // | | | - t = s != 1 ? (t != 0 ? t - 1 : task_queue_depth_ - 1) : t; - --s; - - queued_task_count_.fetch_sub (1, std::memory_order_release); - - td.thunk (*this, ql, &td.data); - ql.lock (); - - // Restore the old mark (which we might have to adjust). - // - if (s == 0) - m = t; // Reset the mark. - else if (a) - m = task_queue_depth_; // Disable the mark. - else - // What happens if head goes past the old mark? In this case we will - // get into the empty queue state before we end up making any (wrong) - // decisions based on this value. Unfortunately there is no way to - // detect this (and do some sanity asserts) since things can wrap - // around. - // - // To put it another way, the understanding here is that after the - // task returns we will either have an empty queue or there will still - // be tasks between the old mark and the current tail, something along - // these lines: - // - // OOOOOXXXXOOO - // | | | - // m h t - // - m = om; - } - - // Each thread has its own queue which are stored in this list. - // - std::list task_queues_; - - // TLS cache of thread's task queue. - // - static -#ifdef __cpp_thread_local - thread_local -#else - __thread -#endif - task_queue* task_queue_; - - task_queue& - create_queue (); - }; -} - -#include - -#endif // BUILD2_SCHEDULER diff --git a/build2/scheduler.cxx b/build2/scheduler.cxx index a453cb3..fdf4121 100644 --- a/build2/scheduler.cxx +++ b/build2/scheduler.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include diff --git a/build2/scheduler.hxx b/build2/scheduler.hxx new file mode 100644 index 0000000..ad14c56 --- /dev/null +++ b/build2/scheduler.hxx @@ -0,0 +1,593 @@ +// file : build2/scheduler.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SCHEDULER_HXX +#define BUILD2_SCHEDULER_HXX + +#include +#include +#include +#include +#include // aligned_storage, etc +#include + +#include +#include + +namespace build2 +{ + // Scheduler of tasks and threads. Works best for "substantial" tasks (e.g., + // running a process), where in comparison thread synchronization overhead + // is negligible. + // + // A thread (called "master") may need to perform several tasks which can be + // done in parallel (e.g., update all the prerequisites or run all the + // tests). To acomplish this, the master, via a call to async(), can ask the + // scheduler to run a task in another thread (called "helper"). If a helper + // is available, then the task is executed asynchronously by such helper. + // Otherwise, the task is (normally) executed synchronously as part of the + // wait() call below. However, in certain cases (serial execution or full + // queue), the task may be executed synchronously as part of the async() + // call itself. Once the master thread has scheduled all the tasks, it calls + // wait() to await for their completion. + // + // The scheduler makes sure that only a certain number of threads (for + // example, the number of available hardware threads) are "active" at any + // given time. When a master thread calls wait(), it is "suspended" until + // all its asynchronous tasks are completed (at which point it becomes + // "ready"). A suspension of a master results in either another ready master + // being "resumed" or another helper thread becoming available. + // + // On completion of a task a helper thread returns to the scheduler which + // can again lead either to a ready master being resumed (in which case the + // helper is suspended) or the helper becoming available to perform another + // task. + // + // Note that suspended threads are not reused as helpers. Rather, a new + // helper thread is always created if none is available. This is done to + // allow a ready master to continue as soon as possible. If it were reused + // as a helper, then it could be blocked on a nested wait() further down the + // stack. All this means that the number of threads created by the scheduler + // will normally exceed the maximum active allowed. + // + class scheduler + { + public: + using atomic_count = std::atomic; + + // F should return void and not throw any exceptions. The way the result + // of a task is communicated back to the master thread is ad hoc, usually + // via "out" arguments. Such result(s) can only be retrieved by the master + // once its task count reaches the start count. + // + // The argument passing semantics is the same as for std::thread. In + // particular, lvalue-references are passed as copies (use ref()/cref() + // for the by-reference semantics), except the case where the task is + // executed synchronously and as part of the async() call itself (this + // subtlety can become important when passing shared locks; you would + // only want it to be copied if the task is queued). + // + // Return true if the task was queued and false if it was executed + // synchronously. + // + // If the scheduler is shutdown, throw system_error(ECANCELED). + // + template + bool + async (size_t start_count, atomic_count& task_count, F&&, A&&...); + + template + bool + async (atomic_count& task_count, F&& f, A&&... a) + { + return async (0, task_count, forward (f), forward (a)...); + } + + // Wait until the task count reaches the start count or less. If the + // scheduler is shutdown while waiting, throw system_error(ECANCELED). + // Return the value of task count. Note that this is a synchronizaiton + // point (i.e., the task count is checked with memory_order_acquire). + // + // Note that it is valid to wait on another thread's task count (that is, + // without making any async() calls in this thread). However, if the start + // count differs from the one passed to async(), then whomever sets the + // start count to this alternative value must also call resume() below + // in order to signal waiting threads. + // + // Note also that in this case (waiting on someone else's start count), + // the async() call could execute the tasks synchronously without ever + // incrementing the task count. Thus if waiting on another thread's start + // count starts before/during async() calls, then it must be "gated" with + // an alternative (lower) start count. + // + // Finally, if waiting on someone else's start count, it may be unsafe + // (from the deadlock's point of view) to continue working through our own + // queue (i.e., we may block waiting on a task that has been queued before + // us which in turn may end up waiting on "us"). + // + enum work_queue + { + work_none, // Don't work own queue. + work_one, // Work own queue rechecking the task count after every task. + work_all // Work own queue before rechecking the task count. + }; + + size_t + wait (size_t start_count, + const atomic_count& task_count, + work_queue = work_all); + + size_t + wait (const atomic_count& task_count, work_queue wq = work_all) + { + return wait (0, task_count, wq); + } + + // Resume threads waiting on this task count. + // + void + resume (const atomic_count& task_count); + + // An active thread that is about to wait for potentially significant time + // on something other than task_count (e.g., mutex, condition variable) + // should deactivate itself with the scheduler and then reactivate once + // done waiting. + // + void + deactivate (); + + void + activate (bool collision = false); + + // Startup and shutdown. + // + public: + // Unless already shut down, call shutdown() but ignore errors. + // + ~scheduler (); + + // Create a shut down scheduler. + // + scheduler () = default; + + // Create a started up scheduler. + // + // The initial active argument is the number of threads to assume are + // already active (e.g., the calling thread). It must not be 0 (since + // someone has to schedule the first task). + // + // If the maximum threads or task queue depth arguments are unspecified, + // then appropriate defaults are used. + // + scheduler (size_t max_active, + size_t init_active = 1, + size_t max_threads = 0, + size_t queue_depth = 0) + { + startup (max_active, init_active, max_threads, queue_depth); + } + + // Start the scheduler. + // + void + startup (size_t max_active, + size_t init_active = 1, + size_t max_threads = 0, + size_t queue_depth = 0); + + // Return true if the scheduler was started up. + // + // Note: can only be called from threads that have observed creation, + // startup, or shutdown. + // + bool + started () const {return !shutdown_;} + + // Tune a started up scheduler. + // + // Currently one cannot increase the number of max_active. Pass 0 to + // restore the initial value. + // + // Note that tuning can only be done while the scheduler is inactive, that + // is, no threads are executing a task or are suspended. For example, in a + // setup with a single initial active thread that would be after a return + // from the top-level wait() call. + // + void + tune (size_t max_active); + + // Return true if the scheduler is running serial. + // + // Note: can only be called from threads that have observed startup. + // + bool + serial () const {return max_active_ == 1;} + + // Wait for all the helper threads to terminate. Throw system_error on + // failure. Note that the initially active threads are not waited for. + // Return scheduling statistics. + // + struct stat + { + size_t thread_max_active = 0; // max # of active threads allowed. + size_t thread_max_total = 0; // max # of total threads allowed. + size_t thread_helpers = 0; // # of helper threads created. + size_t thread_max_waiting = 0; // max # of waiters at any time. + + size_t task_queue_depth = 0; // # of entries in a queue (capacity). + size_t task_queue_full = 0; // # of times task queue was full. + size_t task_queue_remain = 0; // # of tasks remaining in queue. + + size_t wait_queue_slots = 0; // # of wait slots (buckets). + size_t wait_queue_collisions = 0; // # of times slot had been occupied. + }; + + stat + shutdown (); + + // If initially active thread(s) (besides the one that calls startup()) + // exist before the call to startup(), then they must call join() before + // executing any tasks. The two common cases where you don't have to call + // join are a single active thread that calls startup()/shutdown() or + // active thread(s) that are created after startup(). + // + void + join () + { + assert (task_queue_ = nullptr); + + // Lock the mutex to make sure the values set in startup() are visible + // in this thread. + // + lock l (mutex_); + } + + // If initially active thread(s) participate in multiple schedulers and/or + // sessions (intervals between startup() and shutdown()), then they must + // call leave() before joining another scheduler/session. Note that this + // applies to the active thread that calls shutdown(). Note that a thread + // can only participate in one scheduler at a time. + // + void + leave () + { + task_queue_ = nullptr; + } + + // Return the number of hardware threads or 0 if unable to determine. + // + static size_t + hardware_concurrency () + { + return std::thread::hardware_concurrency (); + } + + // Return a prime number that can be used as a lock shard size that's + // appropriate for the scheduler's concurrency. Use power of two values + // for mul for higher-contention shards and for div for lower-contention + // ones. Always return 1 for serial execution. + // + // Note: can only be called from threads that have observed startup. + // + size_t + shard_size (size_t mul = 1, size_t div = 1) const; + + private: + using lock = std::unique_lock; + + void + activate_helper (lock&); + + void + create_helper (lock&); + + // We restrict ourselves to a single pointer as an argument in hope of + // a small object optimization. + // + static void + helper (void*); + + size_t + suspend (size_t start_count, const atomic_count& task_count); + + // Task encapsulation. + // + template + struct task_type + { + using func_type = std::decay_t; + using args_type = std::tuple...>; + + atomic_count* task_count; + size_t start_count; + func_type func; + args_type args; + + template + void + thunk (std::index_sequence) noexcept + { + move (func) (std::get (move (args))...); + } + }; + + template + static void + task_thunk (scheduler&, lock&, void*); + + template + static std::decay_t + decay_copy (T&& x) {return forward (x);} + + private: + std::mutex mutex_; + + bool shutdown_ = true; // Shutdown flag. + + // The constraints that we must maintain: + // + // active <= max_active + // (init_active + helpers) <= max_threads (soft; see activate_helper()) + // + // Note that the first three are immutable between startup() and + // shutdown() so can be accessed without a lock (but see join()). + // + size_t init_active_ = 0; // Initially active threads. + size_t max_active_ = 0; // Maximum number of active threads. + size_t max_threads_ = 0; // Maximum number of total threads. + + size_t helpers_ = 0; // Number of helper threads created so far. + + // Every thread that we manage must be accounted for in one of these + // counters. And their sum should equal (init_active + helpers). + // + size_t active_ = 0; // Active master threads executing a task. + size_t idle_ = 0; // Idle helper threads waiting for a task. + size_t waiting_ = 0; // Suspended master threads waiting for their tasks. + size_t ready_ = 0; // Ready master thread waiting to become active. + size_t starting_ = 0; // Helper threads starting up. + + // Original values (as specified during startup) that can be altered via + // tuning. + // + size_t orig_max_active_ = 0; + + std::condition_variable idle_condv_; // Idle helpers queue. + std::condition_variable ready_condv_; // Ready masters queue. + + // Statistics counters. + // + size_t stat_max_waiters_; + size_t stat_wait_collisions_; + + // Wait queue. + // + // A wait slot blocks a bunch of threads. When they are (all) unblocked, + // they re-examine their respective conditions and either carry on or + // block again. + // + // The wait queue is a shard of slots. A thread picks a slot based on the + // address of its task count variable. How many slots do we need? This + // depends on the number of waiters that we can have which cannot be + // greater than the total number of threads. + // + // The pointer to the task count is used to identify the already waiting + // group of threads for collision statistics. + // + struct wait_slot + { + std::mutex mutex; + std::condition_variable condv; + size_t waiters = 0; + const atomic_count* task_count; + bool shutdown = true; + }; + + size_t wait_queue_size_; // Proportional to max_threads. + unique_ptr wait_queue_; + + // Task queue. + // + // Each queue has its own mutex plus we have an atomic total count of the + // queued tasks. Note that it should only be modified while holding one + // of the queue lock. + // + atomic_count queued_task_count_; + + // For now we only support trivially-destructible tasks. + // + struct task_data + { + std::aligned_storage::type data; + void (*thunk) (scheduler&, lock&, void*); + }; + + // We have two requirements: Firstly, we want to keep the master thread + // (the one that called wait()) busy working though its own queue for as + // long as possible before (if at all) it "reincarnates" as a helper. The + // main reason for this is the limited number of helpers we can create. + // + // Secondly, we don't want to block wait() longer than necessary since the + // master thread can do some work with the result. Plus, overall, we want + // to "unwind" task hierarchies as soon as possible since they hold up + // resources such as thread's stack. All this means that the master thread + // can only work through tasks that it has queued at this "level" of the + // async()/wait() calls since we know that wait() cannot return until + // they are done. + // + // To satisfy the first requirement, the master and helper threads get the + // tasks from different ends of the queue: master from the back while + // helpers from the front. And the master always adds new tasks to the + // back. + // + // To satisfy the second requirement, the master thread stores the index + // of the first task it has queued at this "level" and makes sure it + // doesn't try to deque any task beyond that. + // + size_t task_queue_depth_; // Multiple of max_active. + + struct task_queue + { + std::mutex mutex; + bool shutdown = false; + + size_t stat_full = 0; // Number of times push() returned NULL. + + // Our task queue is circular with head being the index of the first + // element and tail -- of the last. Since this makes the empty and one + // element cases indistinguishable, we also keep the size. + // + // The mark is an index somewhere between (figuratively speaking) head + // and tail, if enabled. If the mark is hit, then it is disabled until + // the queue becomes empty or it is reset by a push. + // + size_t head = 0; + size_t mark = 0; + size_t tail = 0; + size_t size = 0; + + unique_ptr data; + + task_queue (size_t depth): data (new task_data[depth]) {} + }; + + // Task queue API. Expects the queue mutex to be locked. + // + + // Push a new task to the queue returning a pointer to the task data to be + // filled or NULL if the queue is full. + // + task_data* + push (task_queue& tq) + { + size_t& s (tq.size); + size_t& t (tq.tail); + size_t& m (tq.mark); + + if (s != task_queue_depth_) + { + // normal wrap empty + // | | | + t = s != 0 ? (t != task_queue_depth_ - 1 ? t + 1 : 0) : t; + s++; + + if (m == task_queue_depth_) // Enable the mark if first push. + m = t; + + queued_task_count_.fetch_add (1, std::memory_order_release); + return &tq.data[t]; + } + + return nullptr; + } + + bool + empty_front (task_queue& tq) const {return tq.size == 0;} + + void + pop_front (task_queue& tq, lock& ql) + { + size_t& s (tq.size); + size_t& h (tq.head); + size_t& m (tq.mark); + + bool a (h == m); // Adjust mark? + task_data& td (tq.data[h]); + + // normal wrap empty + // | | | + h = s != 1 ? (h != task_queue_depth_ - 1 ? h + 1 : 0) : h; + + if (--s == 0 || a) + m = h; // Reset or adjust the mark. + + queued_task_count_.fetch_sub (1, std::memory_order_release); + + // The thunk moves the task data to its stack, releases the lock, + // and continues to execute the task. + // + td.thunk (*this, ql, &td.data); + ql.lock (); + } + + bool + empty_back (task_queue& tq) const + { + return tq.size == 0 || tq.mark == task_queue_depth_; + } + + void + pop_back (task_queue& tq, lock& ql) + { + size_t& s (tq.size); + size_t& t (tq.tail); + size_t& m (tq.mark); + + bool a (t == m); // Adjust mark? + + task_data& td (tq.data[t]); + + // Save the old queue mark and disable it in case the task we are about + // to run adds sub-tasks. The first push(), if any, will reset it. + // + size_t om (m); + m = task_queue_depth_; + + // normal wrap empty + // | | | + t = s != 1 ? (t != 0 ? t - 1 : task_queue_depth_ - 1) : t; + --s; + + queued_task_count_.fetch_sub (1, std::memory_order_release); + + td.thunk (*this, ql, &td.data); + ql.lock (); + + // Restore the old mark (which we might have to adjust). + // + if (s == 0) + m = t; // Reset the mark. + else if (a) + m = task_queue_depth_; // Disable the mark. + else + // What happens if head goes past the old mark? In this case we will + // get into the empty queue state before we end up making any (wrong) + // decisions based on this value. Unfortunately there is no way to + // detect this (and do some sanity asserts) since things can wrap + // around. + // + // To put it another way, the understanding here is that after the + // task returns we will either have an empty queue or there will still + // be tasks between the old mark and the current tail, something along + // these lines: + // + // OOOOOXXXXOOO + // | | | + // m h t + // + m = om; + } + + // Each thread has its own queue which are stored in this list. + // + std::list task_queues_; + + // TLS cache of thread's task queue. + // + static +#ifdef __cpp_thread_local + thread_local +#else + __thread +#endif + task_queue* task_queue_; + + task_queue& + create_queue (); + }; +} + +#include + +#endif // BUILD2_SCHEDULER_HXX diff --git a/build2/scope b/build2/scope deleted file mode 100644 index 1ce28fc..0000000 --- a/build2/scope +++ /dev/null @@ -1,375 +0,0 @@ -// file : build2/scope -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_SCOPE -#define BUILD2_SCOPE - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace build2 -{ - class scope - { - public: - // Absolute and normalized. - // - const dir_path& out_path () const {return *out_path_;} - const dir_path& src_path () const {return *src_path_;} - - // The first is a pointer to the key in scope_map. The second is a pointer - // to the src_root/base variable value, if any (i.e., it can be NULL). - // - const dir_path* out_path_ = nullptr; - const dir_path* src_path_ = nullptr; - - bool - root () const {return root_ == this;} - - scope* parent_scope () {return parent_;} - const scope* parent_scope () const {return parent_;} - - // Root scope of this scope or NULL if this scope is not (yet) - // in any (known) project. Note that if the scope itself is - // root, then this function return this. To get to the outer - // root, query the root scope of the parent. - // - scope* root_scope () {return root_;} - const scope* root_scope () const {return root_;} - - // Root scope of a strong amalgamation of this scope or NULL if - // this scope is not (yet) in any (known) project. If there is - // no strong amalgamation, then this function returns the root - // scope of the project (in other words, in this case a project - // is treated as its own strong amalgamation). - // - scope* strong_scope (); - const scope* strong_scope () const; - - // Root scope of the outermost amalgamation or NULL if this scope is not - // (yet) in any (known) project. If there is no amalgamation, then this - // function returns the root scope of the project (in other words, in this - // case a project is treated as its own amalgamation). - // - scope* weak_scope (); - const scope* weak_scope () const; - - // Variables. - // - public: - variable_map vars; - - // Lookup, including in outer scopes. If you only want to lookup in this - // scope, do it on the the variables map directly (and note that there - // will be no overrides). - // - lookup - operator[] (const variable& var) const - { - return find (var).first; - } - - lookup - operator[] (const variable* var) const // For cached variables. - { - assert (var != nullptr); - return operator[] (*var); - } - - lookup - operator[] (const string& name) const - { - const variable* var (var_pool.find (name)); - return var != nullptr ? operator[] (*var) : lookup (); - } - - // As above, but include target type/pattern-specific variables. - // - lookup - find (const variable& var, const target_key& tk) const - { - return find (var, tk.type, tk.name).first; - } - - lookup - find (const variable& var, const target_type& tt, const string& tn) const - { - return find (var, &tt, &tn).first; - } - - pair - find (const variable& var, - const target_type* tt = nullptr, - const string* tn = nullptr) const - { - auto p (find_original (var, tt, tn)); - return var.override == nullptr ? p : find_override (var, move (p)); - } - - // Implementation details (used by scope target lookup). The start_depth - // can be used to skip a number of initial lookups. - // - pair - find_original ( - const variable&, - const target_type* tt = nullptr, const string* tn = nullptr, - const target_type* gt = nullptr, const string* gn = nullptr, - size_t start_depth = 1) const; - - pair - find_override (const variable&, - pair original, - bool target = false) const; - - // Return a value suitable for assignment (or append if you only want to - // append to the value from this scope). If the value does not exist in - // this scope's map, then a new one with the NULL value is added and - // returned. Otherwise the existing value is returned. - // - value& - assign (const variable& var) {return vars.assign (var);} - - value& - assign (const variable* var) // For cached variables. - { - assert (var != nullptr); - return vars.assign (*var); - } - - value& - assign (string name) - { - return assign (variable_pool::instance.insert (move (name))); - } - - // Assign a typed non-overridable variable with normal visibility. - // - template - value& - assign (string name) - { - return vars.assign (variable_pool::instance.insert (move (name))); - } - - // Return a value suitable for appending. If the variable does not - // exist in this scope's map, then outer scopes are searched for - // the same variable. If found then a new variable with the found - // value is added to this scope and returned. Otherwise this - // function proceeds as assign(). - // - value& - append (const variable&); - - // Target type/pattern-specific variables. - // - variable_type_map target_vars; - - // Variable override cache (project root and global scope only). - // - // The key is the variable plus the innermost (scope-wise) variable map to - // which this override applies. See find_override() for details. - // - // Note: since it can be modified on any lookup (including during the - // execute phase), the cache is protected by its own mutex shard. - // - mutable - variable_cache> - override_cache; - - // Meta/operations supported by this project (set on the root - // scope only). - // - public: - build2::meta_operations meta_operations; - build2::operations operations; - - using path_type = build2::path; - - // Set of buildfiles already loaded for this scope. The included - // buildfiles are checked against the project's root scope while - // imported -- against the global scope (global_scope). - // - std::unordered_set buildfiles; - - // Target types. - // - public: - target_type_map target_types; - - const target_type* - find_target_type (const string&, const scope** = nullptr) const; - - // Given a name, figure out its type, taking into account extensions, - // special names (e.g., '.' and '..'), or anything else that might be - // relevant. Also process the name (in place) by extracting the - // extension, adjusting dir/value, etc., (note that the dir is not - // necessarily normalized). Return NULL if not found. - // - const target_type* - find_target_type (name&, optional& ext) const; - - // Dynamically derive a new target type from an existing one. Return the - // reference to the target type and an indicator of whether it was - // actually created. - // - pair, bool> - derive_target_type (const string& name, const target_type& base); - - template - pair, bool> - derive_target_type (const string& name) - { - return derive_target_type (name, T::static_type); - } - - // Rules. - // - public: - rule_map rules; - - // Modules. - // - public: - loaded_module_map modules; // Only on root scope. - - public: - // RW access. - // - scope& - rw () const - { - assert (phase == run_phase::load); - return const_cast (*this); - } - - // RW access to global scope (RO via global global_scope below). - // - scope& - global () {return *global_;} - - public: - static scope* global_; // Normally not accessed directly. - - private: - friend class parser; - friend class scope_map; - friend class temp_scope; - - // These two from set strong_. - // - friend void create_bootstrap_outer (scope&); - friend scope& create_bootstrap_inner (scope&, const dir_path&); - - explicit - scope (bool global): vars (global), target_vars (global) {} - - scope* parent_; - scope* root_; - scope* strong_ = nullptr; // Only set on root sopes. - // NULL means no strong amalgamtion. - }; - - // Temporary scope. The idea is to be able to create a temporary scope in - // order not to change the variables in the current scope. Such a scope is - // not entered in to the scope map. As a result it can only be used as a - // temporary set of variables. In particular, defining targetsdirectly in - // such a scope will surely end up badly. Defining any nested scopes will be - // as if defining such a scope in the parent (since path() returns parent's - // path). - // - class temp_scope: public scope - { - public: - temp_scope (scope& p) - : scope (false) // Not global. - { - out_path_ = p.out_path_; - src_path_ = p.src_path_; - parent_ = &p; - root_ = p.root_; - // No need to copy strong_ since we are never root scope. - } - }; - - // Scope map. - // - // Protected by the model mutex. Note that the scope map is only for paths - // from the out tree. - // - using scope_map_base = butl::dir_path_map; - - class scope_map: public scope_map_base - { - public: - // Note that we assume the first insertion into the map is always the - // global scope with empty key. - // - iterator - insert (const dir_path&, bool root); - - // Find the most qualified scope that encompasses this path. - // - const scope& - find (const dir_path& d) const - { - return const_cast (this)->find (d); - } - - const scope& - find (const path& p) const - { - // Natural thing to do here would be to call find (p.directory ()). - // However, there could be a situation where the passed path is a - // directory (i.e., the calling code does not know what it is dealing - // with), so let's use the whole path. - // - return find (path_cast (p)); - } - - // RW access. - // - public: - scope_map& - rw () const - { - assert (phase == run_phase::load); - return const_cast (*this); - } - - scope_map& - rw (scope&) const {return const_cast (*this);} - - private: - static scope_map instance; - - // Entities that can access bypassing the lock proof. - // - friend variable_overrides reset (const strings&); - - scope& - find (const dir_path&); - - public: - static const scope_map& cinstance; // For var_pool initialization. - }; - - extern const scope_map& scopes; - extern const scope* global_scope; -} - -#include - -#endif // BUILD2_SCOPE diff --git a/build2/scope.cxx b/build2/scope.cxx index 59b2ee2..1cbb815 100644 --- a/build2/scope.cxx +++ b/build2/scope.cxx @@ -2,10 +2,10 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include +#include +#include using namespace std; diff --git a/build2/scope.hxx b/build2/scope.hxx new file mode 100644 index 0000000..2be2b3a --- /dev/null +++ b/build2/scope.hxx @@ -0,0 +1,375 @@ +// file : build2/scope.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SCOPE_HXX +#define BUILD2_SCOPE_HXX + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + class scope + { + public: + // Absolute and normalized. + // + const dir_path& out_path () const {return *out_path_;} + const dir_path& src_path () const {return *src_path_;} + + // The first is a pointer to the key in scope_map. The second is a pointer + // to the src_root/base variable value, if any (i.e., it can be NULL). + // + const dir_path* out_path_ = nullptr; + const dir_path* src_path_ = nullptr; + + bool + root () const {return root_ == this;} + + scope* parent_scope () {return parent_;} + const scope* parent_scope () const {return parent_;} + + // Root scope of this scope or NULL if this scope is not (yet) + // in any (known) project. Note that if the scope itself is + // root, then this function return this. To get to the outer + // root, query the root scope of the parent. + // + scope* root_scope () {return root_;} + const scope* root_scope () const {return root_;} + + // Root scope of a strong amalgamation of this scope or NULL if + // this scope is not (yet) in any (known) project. If there is + // no strong amalgamation, then this function returns the root + // scope of the project (in other words, in this case a project + // is treated as its own strong amalgamation). + // + scope* strong_scope (); + const scope* strong_scope () const; + + // Root scope of the outermost amalgamation or NULL if this scope is not + // (yet) in any (known) project. If there is no amalgamation, then this + // function returns the root scope of the project (in other words, in this + // case a project is treated as its own amalgamation). + // + scope* weak_scope (); + const scope* weak_scope () const; + + // Variables. + // + public: + variable_map vars; + + // Lookup, including in outer scopes. If you only want to lookup in this + // scope, do it on the the variables map directly (and note that there + // will be no overrides). + // + lookup + operator[] (const variable& var) const + { + return find (var).first; + } + + lookup + operator[] (const variable* var) const // For cached variables. + { + assert (var != nullptr); + return operator[] (*var); + } + + lookup + operator[] (const string& name) const + { + const variable* var (var_pool.find (name)); + return var != nullptr ? operator[] (*var) : lookup (); + } + + // As above, but include target type/pattern-specific variables. + // + lookup + find (const variable& var, const target_key& tk) const + { + return find (var, tk.type, tk.name).first; + } + + lookup + find (const variable& var, const target_type& tt, const string& tn) const + { + return find (var, &tt, &tn).first; + } + + pair + find (const variable& var, + const target_type* tt = nullptr, + const string* tn = nullptr) const + { + auto p (find_original (var, tt, tn)); + return var.override == nullptr ? p : find_override (var, move (p)); + } + + // Implementation details (used by scope target lookup). The start_depth + // can be used to skip a number of initial lookups. + // + pair + find_original ( + const variable&, + const target_type* tt = nullptr, const string* tn = nullptr, + const target_type* gt = nullptr, const string* gn = nullptr, + size_t start_depth = 1) const; + + pair + find_override (const variable&, + pair original, + bool target = false) const; + + // Return a value suitable for assignment (or append if you only want to + // append to the value from this scope). If the value does not exist in + // this scope's map, then a new one with the NULL value is added and + // returned. Otherwise the existing value is returned. + // + value& + assign (const variable& var) {return vars.assign (var);} + + value& + assign (const variable* var) // For cached variables. + { + assert (var != nullptr); + return vars.assign (*var); + } + + value& + assign (string name) + { + return assign (variable_pool::instance.insert (move (name))); + } + + // Assign a typed non-overridable variable with normal visibility. + // + template + value& + assign (string name) + { + return vars.assign (variable_pool::instance.insert (move (name))); + } + + // Return a value suitable for appending. If the variable does not + // exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this + // function proceeds as assign(). + // + value& + append (const variable&); + + // Target type/pattern-specific variables. + // + variable_type_map target_vars; + + // Variable override cache (project root and global scope only). + // + // The key is the variable plus the innermost (scope-wise) variable map to + // which this override applies. See find_override() for details. + // + // Note: since it can be modified on any lookup (including during the + // execute phase), the cache is protected by its own mutex shard. + // + mutable + variable_cache> + override_cache; + + // Meta/operations supported by this project (set on the root + // scope only). + // + public: + build2::meta_operations meta_operations; + build2::operations operations; + + using path_type = build2::path; + + // Set of buildfiles already loaded for this scope. The included + // buildfiles are checked against the project's root scope while + // imported -- against the global scope (global_scope). + // + std::unordered_set buildfiles; + + // Target types. + // + public: + target_type_map target_types; + + const target_type* + find_target_type (const string&, const scope** = nullptr) const; + + // Given a name, figure out its type, taking into account extensions, + // special names (e.g., '.' and '..'), or anything else that might be + // relevant. Also process the name (in place) by extracting the + // extension, adjusting dir/value, etc., (note that the dir is not + // necessarily normalized). Return NULL if not found. + // + const target_type* + find_target_type (name&, optional& ext) const; + + // Dynamically derive a new target type from an existing one. Return the + // reference to the target type and an indicator of whether it was + // actually created. + // + pair, bool> + derive_target_type (const string& name, const target_type& base); + + template + pair, bool> + derive_target_type (const string& name) + { + return derive_target_type (name, T::static_type); + } + + // Rules. + // + public: + rule_map rules; + + // Modules. + // + public: + loaded_module_map modules; // Only on root scope. + + public: + // RW access. + // + scope& + rw () const + { + assert (phase == run_phase::load); + return const_cast (*this); + } + + // RW access to global scope (RO via global global_scope below). + // + scope& + global () {return *global_;} + + public: + static scope* global_; // Normally not accessed directly. + + private: + friend class parser; + friend class scope_map; + friend class temp_scope; + + // These two from set strong_. + // + friend void create_bootstrap_outer (scope&); + friend scope& create_bootstrap_inner (scope&, const dir_path&); + + explicit + scope (bool global): vars (global), target_vars (global) {} + + scope* parent_; + scope* root_; + scope* strong_ = nullptr; // Only set on root sopes. + // NULL means no strong amalgamtion. + }; + + // Temporary scope. The idea is to be able to create a temporary scope in + // order not to change the variables in the current scope. Such a scope is + // not entered in to the scope map. As a result it can only be used as a + // temporary set of variables. In particular, defining targetsdirectly in + // such a scope will surely end up badly. Defining any nested scopes will be + // as if defining such a scope in the parent (since path() returns parent's + // path). + // + class temp_scope: public scope + { + public: + temp_scope (scope& p) + : scope (false) // Not global. + { + out_path_ = p.out_path_; + src_path_ = p.src_path_; + parent_ = &p; + root_ = p.root_; + // No need to copy strong_ since we are never root scope. + } + }; + + // Scope map. + // + // Protected by the model mutex. Note that the scope map is only for paths + // from the out tree. + // + using scope_map_base = butl::dir_path_map; + + class scope_map: public scope_map_base + { + public: + // Note that we assume the first insertion into the map is always the + // global scope with empty key. + // + iterator + insert (const dir_path&, bool root); + + // Find the most qualified scope that encompasses this path. + // + const scope& + find (const dir_path& d) const + { + return const_cast (this)->find (d); + } + + const scope& + find (const path& p) const + { + // Natural thing to do here would be to call find (p.directory ()). + // However, there could be a situation where the passed path is a + // directory (i.e., the calling code does not know what it is dealing + // with), so let's use the whole path. + // + return find (path_cast (p)); + } + + // RW access. + // + public: + scope_map& + rw () const + { + assert (phase == run_phase::load); + return const_cast (*this); + } + + scope_map& + rw (scope&) const {return const_cast (*this);} + + private: + static scope_map instance; + + // Entities that can access bypassing the lock proof. + // + friend variable_overrides reset (const strings&); + + scope& + find (const dir_path&); + + public: + static const scope_map& cinstance; // For var_pool initialization. + }; + + extern const scope_map& scopes; + extern const scope* global_scope; +} + +#include + +#endif // BUILD2_SCOPE_HXX diff --git a/build2/search b/build2/search deleted file mode 100644 index 92521bc..0000000 --- a/build2/search +++ /dev/null @@ -1,36 +0,0 @@ -// file : build2/search -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_SEARCH -#define BUILD2_SEARCH - -#include -#include - -namespace build2 -{ - class target; - class prerequisite_key; - - // Search for an existing target in this prerequisite's scope. - // - const target* - search_existing_target (const prerequisite_key&); - - // Search for an existing file in the scope's src directory. Prerequisite - // directory should be relative. - // - // Originally the plan was to have a target-type specific variable that - // contains the search paths. But there wasn't any need for this yet. - // - const target* - search_existing_file (const prerequisite_key&); - - // Create a new target in this prerequisite's scope. - // - const target& - create_new_target (const prerequisite_key&); -} - -#endif // BUILD2_SEARCH diff --git a/build2/search.cxx b/build2/search.cxx index f7864e9..6be62b2 100644 --- a/build2/search.cxx +++ b/build2/search.cxx @@ -2,15 +2,15 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // file_mtime() +#include // file_mtime() -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/search.hxx b/build2/search.hxx new file mode 100644 index 0000000..043a216 --- /dev/null +++ b/build2/search.hxx @@ -0,0 +1,36 @@ +// file : build2/search.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SEARCH_HXX +#define BUILD2_SEARCH_HXX + +#include +#include + +namespace build2 +{ + class target; + class prerequisite_key; + + // Search for an existing target in this prerequisite's scope. + // + const target* + search_existing_target (const prerequisite_key&); + + // Search for an existing file in the scope's src directory. Prerequisite + // directory should be relative. + // + // Originally the plan was to have a target-type specific variable that + // contains the search paths. But there wasn't any need for this yet. + // + const target* + search_existing_file (const prerequisite_key&); + + // Create a new target in this prerequisite's scope. + // + const target& + create_new_target (const prerequisite_key&); +} + +#endif // BUILD2_SEARCH_HXX diff --git a/build2/spec b/build2/spec deleted file mode 100644 index 3ec4689..0000000 --- a/build2/spec +++ /dev/null @@ -1,69 +0,0 @@ -// file : build2/spec -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_SPEC -#define BUILD2_SPEC - -#include -#include - -#include - -namespace build2 -{ - class scope; - - struct targetspec - { - typedef build2::name name_type; - - explicit - targetspec (name_type n): name (move (n)) {} - targetspec (dir_path sb, name_type n) - : src_base (move (sb)), name (move (n)) {} - - dir_path src_base; - name_type name; - - // The rest is calculated and cached. - // - scope* root_scope = nullptr; - dir_path out_base; - path buildfile; // Empty if implied. - }; - - struct opspec: vector - { - opspec () = default; - opspec (string n): name (move (n)) {} - - string name; - values params; - }; - - struct metaopspec: vector - { - metaopspec () = default; - metaopspec (string n): name (move (n)) {} - - string name; - values params; - }; - - typedef vector buildspec; - - ostream& - operator<< (ostream&, const targetspec&); - - ostream& - operator<< (ostream&, const opspec&); - - ostream& - operator<< (ostream&, const metaopspec&); - - ostream& - operator<< (ostream&, const buildspec&); -} - -#endif // BUILD2_SPEC diff --git a/build2/spec.cxx b/build2/spec.cxx index c6f386c..da581c8 100644 --- a/build2/spec.cxx +++ b/build2/spec.cxx @@ -2,10 +2,10 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include +#include +#include using namespace std; diff --git a/build2/spec.hxx b/build2/spec.hxx new file mode 100644 index 0000000..2af74db --- /dev/null +++ b/build2/spec.hxx @@ -0,0 +1,69 @@ +// file : build2/spec.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_SPEC_HXX +#define BUILD2_SPEC_HXX + +#include +#include + +#include + +namespace build2 +{ + class scope; + + struct targetspec + { + typedef build2::name name_type; + + explicit + targetspec (name_type n): name (move (n)) {} + targetspec (dir_path sb, name_type n) + : src_base (move (sb)), name (move (n)) {} + + dir_path src_base; + name_type name; + + // The rest is calculated and cached. + // + scope* root_scope = nullptr; + dir_path out_base; + path buildfile; // Empty if implied. + }; + + struct opspec: vector + { + opspec () = default; + opspec (string n): name (move (n)) {} + + string name; + values params; + }; + + struct metaopspec: vector + { + metaopspec () = default; + metaopspec (string n): name (move (n)) {} + + string name; + values params; + }; + + typedef vector buildspec; + + ostream& + operator<< (ostream&, const targetspec&); + + ostream& + operator<< (ostream&, const opspec&); + + ostream& + operator<< (ostream&, const metaopspec&); + + ostream& + operator<< (ostream&, const buildspec&); +} + +#endif // BUILD2_SPEC_HXX diff --git a/build2/target b/build2/target deleted file mode 100644 index 4838dfb..0000000 --- a/build2/target +++ /dev/null @@ -1,1719 +0,0 @@ -// file : build2/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TARGET -#define BUILD2_TARGET - -#include // tags, etc. -#include // aligned_storage -#include - -#include // map_iterator_adapter - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace build2 -{ - class rule; - class scope; - class target; - - extern size_t current_on; // From . - - const target& - search (const target&, const prerequisite&); // From . - - // Target state. - // - enum class target_state: uint8_t - { - // The order of the enumerators is arranged so that their integral values - // indicate whether one "overrides" the other in the "merge" operator| - // (see below). - // - // Note that postponed is "greater" than unchanged since it may result in - // the changed state. - // - unknown, - unchanged, - postponed, - busy, - changed, - failed, - group // Target's state is the group's state. - }; - - ostream& - operator<< (ostream&, target_state); - - inline target_state& - operator |= (target_state& l, target_state r) - { - if (static_cast (r) > static_cast (l)) - l = r; - - return l; - } - - // Recipe. - // - // The returned target state is normally changed or unchanged. If there is - // an error, then the recipe should throw failed rather than returning (this - // is the only exception that a recipe can throw). - // - // The return value of the recipe is used to update the target state. If it - // is target_state::group then the target's state is the group's state. - // - // The recipe may also return postponed in which case the target state is - // assumed to be unchanged (normally this means a prerequisite was postponed - // and while the prerequisite will be re-examined via another dependency, - // this target is done). - // - // Note that max size for the "small capture optimization" in std::function - // ranges (in pointer sizes) from 0 (GCC prior to 5) to 2 (GCC 5) to 6 (VC - // 14u2). With the size ranging (in bytes for 64-bit target) from 32 (GCC) - // to 64 (VC). - // - using recipe_function = target_state (action, const target&); - using recipe = function; - - // Commonly-used recipes. The default recipe executes the action on - // all the prerequisites in a loop, skipping ignored. Specifically, - // for actions with the "first" execution mode, it calls - // execute_prerequisites() while for those with the "last" mode -- - // reverse_execute_prerequisites(); see , - // for details. The group recipe call's the group's - // recipe. - // - extern const recipe empty_recipe; - extern const recipe noop_recipe; - extern const recipe default_recipe; - extern const recipe group_recipe; - - target_state - noop_action (action, const target&); // Defined in . - - target_state - group_action (action, const target&); // Defined in . - - // A view of target group members. - // - struct group_view - { - const target* const* members; // NULL means not yet known. - size_t count; - }; - - // Target. - // - class target - { - optional* ext_; // Reference to value in target_key. - - public: - // For targets that are in the src tree of a project we also keep the - // corresponding out directory. As a result we may end up with multiple - // targets for the same file if we are building multiple configurations of - // the same project at once. We do it this way because, in a sense, a - // target's out directory is its "configuration" (in terms of variables). - // As an example, consider installing the same README file (src) but for - // two different project configurations at once. Which installation - // directory should we use? The answer depends on which configuration you - // ask. - // - // Empty out directory indicates this target is in the out tree (including - // when src == out). We also treat out of project targets as being in the - // out tree. - // - const dir_path dir; // Absolute and normalized. - const dir_path out; // Empty or absolute and normalized. - const string name; - - const string* ext () const; // Return NULL if not specified. - const string& ext (string); - - const dir_path& - out_dir () const {return out.empty () ? dir : out;} - - // Target group to which this target belongs, if any. Note that we assume - // that the group and all its members are in the same scope (for example, - // in variable lookup). We also don't support nested groups (with a small - // exception for ad hoc groups; see below). - // - // The semantics of the interaction between the group and its members and - // what it means to, say, update the group, is unspecified and is - // determined by the group's type. In particular, a group can be created - // out of member types that have no idea they are part of this group - // (e.g., cli.cxx{}). - // - // Normally, however, there are two kinds of groups: "alternatives" and - // "combination". In an alternatives group, normally one of the members is - // selected when the group is mentioned as a prerequisite with, perhaps, - // an exception for special rules, like aliases, where it makes more sense - // to treat the group as a whole. In this case we say that the rule - // "semantically recognizes" the group and picks some of its members. - // - // Updating an alternatives group as a whole can mean updating some subset - // of its members (e.g., lib{}). Or the group may not support this at all - // (e.g., obj{}). - // - // In a combination group, when a group is updated, normally all members - // are updates (and usually with a single command), though there could be - // some members that are omitted, depending on the configuration (e.g., an - // inline file not/being generated). When a combination group is mentioned - // as a prerequisite, the rule is usually interested in the individual - // members rather than the whole group. For example, a C++ compile rule - // would like to "see" the ?xx{} members when it gets a cli.cxx{} group. - // - // Which brings us to the group iteration mode. The target type contains a - // member called see_through that indicates whether the default iteration - // mode for the group should be "see through"; that is, whether we see the - // members or the group itself. For the iteration support itself, see the - // *_prerequisite_members() machinery below. - // - // In a combination group we usually want the state (and timestamp; see - // mtime()) for members to come from the group. This is achieved with the - // special target_state::group state. You would normally also use the - // group_recipe for group members. - // - const target* group = nullptr; - - - // What has been described above is a "normal" group. That is, there is - // a dedicated target type that explicitly serves as a group and there - // is an explicit mechanism for discovering the group's members. - // - // However, sometimes, we may want to create a group on the fly out of a - // normal target type. For example, we have the libs{} target type. But - // on Windows a shared library consist of (at least) two files: the import - // library and the DLL itself. So we somehow need to be able to capture - // that. One approach would be to imply the presence of the second file. - // However, that means that a lot of generic rules (e.g., clean, install, - // etc) will need to know about this special semantics on Windows. Also, - // there would be no convenient way to customize things like extensions, - // etc (for which we use target-specific variables). In other words, it - // would be much easier and more consistent to make these extra files - // proper targets. - // - // So to support this requirement we have "ad hoc" groups. The idea is - // that any target can be turned (by the rule that matched it) into an ad - // hoc group by chaining several targets. Ad hoc groups have a more - // restricted semantics compared to the normal groups. In particular: - // - // - The ad hoc group itself is in a sense its first/primary target. - // - // - Group member's recipes should be set to group_recipe by the group's - // rule. - // - // - Members are discovered lazily, they are only known after the group's - // rule's apply() call. - // - // - Members cannot be used as prerequisites but can be used as targets - // - (e.g., to set variables, etc). - // - // - Members don't have prerequisites. - // - // - Ad hoc group cannot have sub group (of any kind) though an ad hoc - // group can be a sub-group of a normal group. - // - // - Member variable lookup skips the ad hoc group (since the group is - // the first member, this is normally what we want). - // - const_ptr member = nullptr; - - bool - adhoc_group () const - { - // An ad hoc group can be a member of a normal group. - // - return member != nullptr && - (group == nullptr || group->member == nullptr); - } - - bool - adhoc_member () const - { - return group != nullptr && group->member != nullptr; - } - - public: - using action_type = build2::action; - - // You should not call this function directly; rather use - // resolve_group_members() from . - // - virtual group_view - group_members (action_type) const; - - // Note that the returned key "tracks" the target (except for the - // extension). - // - target_key - key () const; - - // Scoping. - // - public: - // Most qualified scope that contains this target. - // - const scope& - base_scope () const; - - // Root scope of a project that contains this target. Note that - // a target can be out of any (known) project root in which case - // this function asserts. If you need to detect this situation, - // then use base_scope().root_scope() expression instead. - // - const scope& - root_scope () const; - - // Root scope of a strong amalgamation that contains this target. - // The same notes as to root_scope() apply. - // - const scope& - strong_scope () const {return *root_scope ().strong_scope ();} - - // Root scope of the outermost amalgamation that contains this target. - // The same notes as to root_scope() apply. - // - const scope& - weak_scope () const {return *root_scope ().weak_scope ();} - - bool - in (const scope& s) const - { - return out_dir ().sub (s.out_path ()); - } - - // Prerequisites. - // - // We use an atomic-empty semantics that allows one to "swap in" a set of - // prerequisites if none were specified. This is used to implement - // "synthesized" dependencies. - // - public: - using prerequisites_type = build2::prerequisites; - - const prerequisites_type& - prerequisites () const; - - // Swap-in a list of prerequisites. Return false if unsuccessful (i.e., - // someone beat us to it). Note that it can be called on const target. - // - bool - prerequisites (prerequisites_type&&) const; - - // Check if there are any prerequisites, taking into account group - // prerequisites. - // - bool - has_prerequisites () const - { - return !prerequisites ().empty () || - (group != nullptr && !group->prerequisites ().empty ()); - } - - private: - friend class parser; - - // Note that the state is also used to synchronize the prerequisites - // value so we use the release-acquire ordering. - // - // 0 - absent - // 1 - being set - // 2 - present - // - atomic prerequisites_state_ {0}; - prerequisites_type prerequisites_; - - static const prerequisites_type empty_prerequisites_; - - // Target-specific variables. - // - public: - variable_map vars; - - // Lookup, including in groups to which this target belongs and then in - // outer scopes (including target type/pattern-specific variables). If you - // only want to lookup in this target, do it on the variable map directly - // (and note that there will be no overrides). - // - lookup - operator[] (const variable& var) const - { - return find (var).first; - } - - lookup - operator[] (const variable* var) const // For cached variables. - { - assert (var != nullptr); - return operator[] (*var); - } - - lookup - operator[] (const string& name) const - { - const variable* var (var_pool.find (name)); - return var != nullptr ? operator[] (*var) : lookup (); - } - - // As above but also return the depth at which the value is found. The - // depth is calculated by adding 1 for each test performed. So a value - // that is from the target will have depth 1. That from the group -- 2. - // From the innermost scope's target type/patter-specific variables -- - // 3. From the innermost scope's variables -- 4. And so on. The idea is - // that given two lookups from the same target, we can say which one came - // earlier. If no value is found, then the depth is set to ~0. - // - // - pair - find (const variable& var) const - { - auto p (find_original (var)); - return var.override == nullptr - ? p - : base_scope ().find_override (var, move (p), true); - } - - // If target_only is true, then only look in target and its target group - // without continuing in scopes. - // - pair - find_original (const variable&, bool target_only = false) const; - - // Return a value suitable for assignment. See scope for details. - // - value& - assign (const variable& var) {return vars.assign (var);} - - // Return a value suitable for appending. See class scope for details. - // - value& - append (const variable&); - - // A target that is not (yet) entered as part of a real dependency - // declaration (for example, that is entered as part of a target-specific - // variable assignment, dependency extraction, etc) is called implied. - // - // The implied flag should only be cleared during the load phase via the - // MT-safe target_set::insert(). - // - public: - bool implied; - - // Target state. - // - public: - // Atomic task count that is used during match and execution to track the - // target's "meta-state" as well as the number of its sub-tasks (e.g., - // busy+1, busy+2, and so on, for instance, number of prerequisites - // being matched or executed). - // - // For each operation in a meta-operation batch (current_on) we have a - // "band" of counts, [touched, executed], that represent the target - // meta-state. Once the next operation is started, this band "moves" thus - // automatically resetting the target to "not yet touched" state for this - // operation. - // - // For match we have a further complication in that we may re-match the - // target and override with a "stronger" recipe thus re-setting the state - // from, say, applied back to touched. - // - // The target is said to be synchronized (in this thread) if we have - // either observed the task count to reach applied or executed or we have - // successfully changed it (via compare_exchange) to locked or busy. If - // the target is synchronized, then we can access and modify (second case) - // its state etc. - // - static const size_t offset_touched = 1; // Has been locked. - static const size_t offset_matched = 2; // Rule has been matched. - static const size_t offset_applied = 3; // Rule has been applied. - static const size_t offset_executed = 4; // Recipe has been executed. - static const size_t offset_locked = 5; // Fast (spin) lock. - static const size_t offset_busy = 6; // Slow (wait) lock. - - static size_t count_base () {return 4 * (current_on - 1);} - - static size_t count_touched () {return offset_touched + count_base ();} - static size_t count_matched () {return offset_matched + count_base ();} - static size_t count_applied () {return offset_applied + count_base ();} - static size_t count_executed () {return offset_executed + count_base ();} - static size_t count_locked () {return offset_locked + count_base ();} - static size_t count_busy () {return offset_busy + count_base ();} - - mutable atomic_count task_count {0}; // Start offset_touched - 1. - - // This function should only be called during match if we have observed - // (synchronization-wise) that the this target has been matched (i.e., - // the rule has been applied) for this action. - // - target_state - matched_state (action a, bool fail = true) const; - - // This function should only be called during execution if we have - // observed (synchronization-wise) that this target has been executed. - // - target_state - executed_state (bool fail = true) const; - - // This function should only be called between match and execute while - // running serially. It returns the group state for the "final" action - // that has been matched (and that will be executed). - // - target_state - serial_state (bool fail = true) const; - - // Number of direct targets that depend on this target in the current - // operation. It is incremented during match and then decremented during - // execution, before running the recipe. As a result, the recipe can - // detect the last chance (i.e., last dependent) to execute the command - // (see also the first/last execution modes in ). - // - mutable atomic_count dependents; - - protected: - // Return fail-untranslated (but group-translated) state assuming the - // target is executed and synchronized. - // - target_state - state () const; - - // Version that should be used during match after the target has been - // matched for this action (see the recipe override). - // - target_state - state (action a) const; - - // Return true if the state comes from the group. Target must be at least - // matched. - // - bool - group_state () const; - - // Raw state, normally not accessed directly. - // - public: - target_state state_ = target_state::unknown; - - // Recipe. - // - public: - using recipe_type = build2::recipe; - using rule_type = build2::rule; - - action_type action; // Action the rule/recipe is for. - - // Matched rule (pointer to hint_rule_map element). Note that in case of a - // direct recipe assignment we may not have a rule. - // - const pair>* rule; - - // Applied recipe. - // - recipe_type recipe_; - - // Note that the target must be locked in order to set the recipe. - // - void - recipe (recipe_type); - - // After the target has been matched and synchronized, check if the target - // is known to be unchanged. Used for optimizations during search & match. - // - bool - unchanged (action_type a) const - { - return state (a) == target_state::unchanged; - } - - // Targets to which prerequisites resolve for this recipe. Note that - // unlike prerequisite::target, these can be resolved to group members. - // NULL means the target should be skipped (or the rule may simply not add - // such a target to the list). - // - // Note also that it is possible the target can vary from action to - // action, just like recipes. We don't need to keep track of the action - // here since the targets will be updated if the recipe is updated, - // normally as part of rule::apply(). - // - // Note that the recipe may modify this list. - // - using prerequisite_targets_type = vector; - mutable prerequisite_targets_type prerequisite_targets; - - // Auxilary data storage. - // - // A rule that matches (i.e., returns true from its match() function) may - // use this pad to pass data between its match and apply functions as well - // as the recipe. After the recipe is executed, the data is destroyed by - // calling data_dtor (if not NULL). The rule should static assert that the - // size of the pad is sufficient for its needs. - // - // Note also that normally at least 2 extra pointers may be stored without - // a dynamic allocation in the returned recipe (small object optimization - // in std::function). So if you need to pass data only between apply() and - // the recipe, then this might be a more convenient way. - // - // Note also that a rule that delegates to another rule may not be able to - // use this mechanism fully since the delegated-to rule may also need the - // data pad. - // - // Currenly the data is not destroyed until the next match. - // - // Note that the recipe may modify the data. - // - static constexpr size_t data_size = sizeof (string) * 10; - mutable std::aligned_storage::type data_pad; - - mutable void (*data_dtor) (void*) = nullptr; - - template ::type>::type> - typename std::enable_if::value,T&>::type - data (R&& d) const - { - assert (sizeof (T) <= data_size && data_dtor == nullptr); - return *new (&data_pad) T (forward (d)); - } - - template ::type>::type> - typename std::enable_if::value,T&>::type - data (R&& d) const - { - assert (sizeof (T) <= data_size && data_dtor == nullptr); - T& r (*new (&data_pad) T (forward (d))); - data_dtor = [] (void* p) {static_cast (p)->~T ();}; - return r; - } - - template - T& - data () const {return *reinterpret_cast (&data_pad);} - - void - clear_data () const - { - if (data_dtor != nullptr) - { - data_dtor (&data_pad); - data_dtor = nullptr; - } - } - - // Target type info and casting. - // - public: - bool - is_a (const target_type& tt) const {return type ().is_a (tt);} - - template - T* - is_a () {return dynamic_cast (this);} - - template - const T* - is_a () const {return dynamic_cast (this);} - - // Unchecked cast. - // - template - T& - as () {return static_cast (*this);} - - template - const T& - as () const {return static_cast (*this);} - - // Dynamic derivation to support define. - // - const target_type* derived_type = nullptr; - - const target_type& - type () const - { - return derived_type != nullptr ? *derived_type : dynamic_type (); - } - - virtual const target_type& dynamic_type () const = 0; - static const target_type static_type; - - public: - virtual - ~target (); - - target (const target&) = delete; - target& operator= (const target&) = delete; - - // The only way to create a target should be via the targets set below. - // - public: - friend class target_set; - - target (dir_path d, dir_path o, string n) - : dir (move (d)), out (move (o)), name (move (n)), - vars (false) // Note: not global. - {} - }; - - // All targets are from the targets set below. - // - inline bool - operator== (const target& x, const target& y) {return &x == &y;} - - inline bool - operator!= (const target& x, const target& y) {return !(x == y);} - - inline ostream& - operator<< (ostream& os, const target& t) {return os << t.key ();} - - // Sometimes it is handy to "mark" a pointer to a target (for example, in - // prerequisite_targets). We use the last 2 bits in a pointer for that (aka - // the "bit stealing" technique). Note that the pointer needs to be unmarked - // before it can be usable so care must be taken in the face of exceptions, - // etc. - // - void - mark (const target*&, uint8_t = 1); - - uint8_t - unmark (const target*&); - - // A "range" that presents the prerequisites of a group and one of - // its members as one continuous sequence, or, in other words, as - // if they were in a single container. The group's prerequisites - // come first followed by the member's. If you need to see them - // in the other direction, iterate in reverse, for example: - // - // for (prerequisite& p: group_prerequisites (t)) - // - // for (prerequisite& p: reverse_iterate (group_prerequisites (t)) - // - // Note that in this case the individual elements of each list will - // also be traversed in reverse, but that's what you usually want, - // anyway. - // - // For constant iteration use const_group_prerequisites(). - // - class group_prerequisites - { - public: - explicit - group_prerequisites (const target& t) - : t_ (t), - g_ (t_.group == nullptr || - t_.group->member != nullptr || // Ad hoc group member. - t_.group->prerequisites ().empty () - ? nullptr : t_.group) {} - - using prerequisites_type = target::prerequisites_type; - using base_iterator = prerequisites_type::const_iterator; - - struct iterator - { - using value_type = base_iterator::value_type; - using pointer = base_iterator::pointer; - using reference = base_iterator::reference; - using difference_type = base_iterator::difference_type; - using iterator_category = std::bidirectional_iterator_tag; - - iterator () {} - iterator (const target* t, - const target* g, - const prerequisites_type* c, - base_iterator i): t_ (t), g_ (g), c_ (c), i_ (i) {} - - iterator& - operator++ () - { - if (++i_ == c_->end () && c_ != &t_->prerequisites ()) - { - c_ = &t_->prerequisites (); - i_ = c_->begin (); - } - return *this; - } - - iterator - operator++ (int) {iterator r (*this); operator++ (); return r;} - - iterator& - operator-- () - { - if (i_ == c_->begin () && c_ == &t_->prerequisites ()) - { - c_ = &g_->prerequisites (); - i_ = c_->end (); - } - - --i_; - return *this; - } - - iterator - operator-- (int) {iterator r (*this); operator-- (); return r;} - - reference operator* () const {return *i_;} - pointer operator-> () const {return i_.operator -> ();} - - friend bool - operator== (const iterator& x, const iterator& y) - { - return x.t_ == y.t_ && x.g_ == y.g_ && x.c_ == y.c_ && x.i_ == y.i_; - } - - friend bool - operator!= (const iterator& x, const iterator& y) {return !(x == y);} - - private: - const target* t_ = nullptr; - const target* g_ = nullptr; - const prerequisites_type* c_ = nullptr; - base_iterator i_; - }; - - using reverse_iterator = std::reverse_iterator; - - iterator - begin () const - { - auto& c ((g_ != nullptr ? *g_ : t_).prerequisites ()); - return iterator (&t_, g_, &c, c.begin ()); - } - - iterator - end () const - { - auto& c (t_.prerequisites ()); - return iterator (&t_, g_, &c, c.end ()); - } - - reverse_iterator - rbegin () const {return reverse_iterator (end ());} - - reverse_iterator - rend () const {return reverse_iterator (begin ());} - - size_t - size () const - { - return t_.prerequisites ().size () + - (g_ != nullptr ? g_->prerequisites ().size () : 0); - } - - private: - const target& t_; - const target* g_; - }; - - // A member of a prerequisite. If 'target' is NULL, then this is the - // prerequisite itself. Otherwise, it is its member. In this case - // 'prerequisite' still refers to the prerequisite. - // - struct prerequisite_member - { - using scope_type = build2::scope; - using target_type = build2::target; - using prerequisite_type = build2::prerequisite; - using target_type_type = build2::target_type; - - const prerequisite_type& prerequisite; - const target_type* target; - - template - bool - is_a () const - { - return target != nullptr - ? target->is_a () != nullptr - : prerequisite.is_a (); - } - - bool - is_a (const target_type_type& tt) const - { - return target != nullptr ? target->is_a (tt) : prerequisite.is_a (tt); - } - - prerequisite_key - key () const - { - return target != nullptr - ? prerequisite_key {prerequisite.proj, target->key (), nullptr} - : prerequisite.key (); - } - - const target_type_type& - type () const - { - return target != nullptr ? target->type () : prerequisite.type; - } - - const string& - name () const - { - return target != nullptr ? target->name : prerequisite.name; - } - - const dir_path& - dir () const - { - return target != nullptr ? target->dir : prerequisite.dir; - } - - const optional& - proj () const - { - // Target cannot be project-qualified. - // - return target != nullptr - ? prerequisite_key::nullproj - : prerequisite.proj; - } - - const scope_type& - scope () const - { - return target != nullptr ? target->base_scope () : prerequisite.scope; - } - - const target_type& - search (const target_type& t) const - { - return target != nullptr ? *target : build2::search (t, prerequisite); - } - - // Return as a new prerequisite instance. - // - prerequisite_type - as_prerequisite () const; - }; - - // It is often stored as the target's auxiliary data so make sure there is - // no destructor overhead. - // - static_assert (std::is_trivially_destructible::value, - "prerequisite_member is not trivially destructible"); - - inline ostream& - operator<< (ostream& os, const prerequisite_member& pm) - { - return os << pm.key (); - } - - // A "range" that presents a sequence of prerequisites (e.g., from - // group_prerequisites()) as a sequence of prerequisite_member's. For each - // group prerequisite you will "see" either the prerequisite itself or all - // its members, depending on the default iteration mode of the target group - // type (ad hoc groups are always see through). You can skip the rest of the - // group members with leave_group() and you can force iteration over the - // members with enter_group(). Usage: - // - // for (prerequisite_member pm: prerequisite_members (a, ...)) - // - // Where ... can be: - // - // t.prerequisites - // reverse_iterate(t.prerequisites) - // group_prerequisites (t) - // reverse_iterate (group_prerequisites (t)) - // - // But use shortcuts instead: - // - // prerequisite_members (a, t) - // reverse_prerequisite_members (a, t) - // group_prerequisite_members (a, t) - // reverse_group_prerequisite_members (a, t) - // - template - class prerequisite_members_range; - - // See-through/ad hoc group members iteration mode. Unless the mode is never, - // ad hoc members are always iterated over. - // - enum class members_mode - { - always, // Iterate over members, assert if not resolvable. - maybe, // Iterate over members if resolvable, group otherwise. - never // Iterate over group (can still use enter_group()). - }; - - template - inline prerequisite_members_range - prerequisite_members (action a, const target& t, - R&& r, - members_mode m = members_mode::always) - { - return prerequisite_members_range (a, t, forward (r), m); - } - - template - class prerequisite_members_range - { - public: - prerequisite_members_range (action a, const target& t, - R&& r, - members_mode m) - : a_ (a), t_ (t), mode_ (m), r_ (forward (r)), e_ (r_.end ()) {} - - using base_iterator = decltype (declval ().begin ()); - - struct iterator - { - using value_type = prerequisite_member; - using pointer = const value_type*; - using reference = const value_type&; - using difference_type = typename base_iterator::difference_type; - using iterator_category = std::forward_iterator_tag; - - iterator (): r_ (nullptr) {} - iterator (const prerequisite_members_range* r, const base_iterator& i) - : r_ (r), i_ (i), g_ {nullptr, 0}, k_ (nullptr) - { - if (r_->mode_ != members_mode::never && - i_ != r_->e_ && - i_->type.see_through) - switch_mode (); - } - - iterator& operator++ (); - iterator operator++ (int) {iterator r (*this); operator++ (); return r;} - - // Skip iterating over the rest of this group's members, if any. Note - // that the only valid operation after this call is to increment the - // iterator. Note that it can be used on ad hoc groups. - // - void - leave_group (); - - // Iterate over this group's members. Return false if the member - // information is not available. Similar to leave_group(), you should - // increment the iterator after calling this function (provided it - // returned true). Note that it cannot be used on ad hoc groups (which - // will be always entered). - // - bool - enter_group (); - - value_type operator* () const - { - const target* t (k_ != nullptr ? k_: - g_.count != 0 ? g_.members[j_ - 1] : nullptr); - - return value_type {*i_, t}; - } - - pointer operator-> () const - { - static_assert ( - std::is_trivially_destructible::value, - "prerequisite_member is not trivially destructible"); - - const target* t (k_ != nullptr ? k_: - g_.count != 0 ? g_.members[j_ - 1] : nullptr); - - return new (&m_) value_type {*i_, t}; - } - - friend bool - operator== (const iterator& x, const iterator& y) - { - return x.i_ == y.i_ && - x.g_.count == y.g_.count && - (x.g_.count == 0 || x.j_ == y.j_) && - x.k_ == y.k_; - } - - friend bool - operator!= (const iterator& x, const iterator& y) {return !(x == y);} - - // What we have here is a state for three nested iteration modes (and - // no, I am not proud of it). The innermost mode is iteration over an ad - // hoc group (k_). Then we have iteration over a normal group (g_ and - // j_). Finally, at the outer level, we have the range itself (i_). - // - // The ad hoc iteration is peculiar in that we only switch to this mode - // once the caller tries to increment past the group itself (which is - // the primary/first member). The reason for this is that the members - // will normally only be known once the caller searched and matched - // the group. - // - // Also, the enter/leave group support is full of ugly, special cases. - // - private: - void - switch_mode (); - - private: - const prerequisite_members_range* r_; - base_iterator i_; - group_view g_; - size_t j_; // 1-based index, to support enter_group(). - const target* k_; // Current member of ad hoc group or NULL. - mutable typename std::aligned_storage::type m_; - }; - - iterator - begin () const {return iterator (this, r_.begin ());} - - iterator - end () const {return iterator (this, e_);} - - private: - action a_; - const target& t_; - members_mode mode_; - R r_; - base_iterator e_; - }; - - // prerequisite_members(t.prerequisites) - // - inline auto - prerequisite_members (action a, target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, t.prerequisites (), m); - } - - inline auto - prerequisite_members (action a, const target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, t.prerequisites (), m); - } - - // prerequisite_members(reverse_iterate(t.prerequisites)) - // - inline auto - reverse_prerequisite_members (action a, target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, reverse_iterate (t.prerequisites ()), m); - } - - inline auto - reverse_prerequisite_members (action a, const target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, reverse_iterate (t.prerequisites ()), m); - } - - // prerequisite_members(group_prerequisites (t)) - // - inline auto - group_prerequisite_members (action a, target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, group_prerequisites (t), m); - } - - inline auto - group_prerequisite_members (action a, const target& t, - members_mode m = members_mode::always) - { - return prerequisite_members (a, t, group_prerequisites (t), m); - } - - // prerequisite_members(reverse_iterate (group_prerequisites (t))) - // - inline auto - reverse_group_prerequisite_members (action a, target& t, - members_mode m = members_mode::always) - { - return prerequisite_members ( - a, t, reverse_iterate (group_prerequisites (t)), m); - } - - inline auto - reverse_group_prerequisite_members (action a, const target& t, - members_mode m = members_mode::always) - { - return prerequisite_members ( - a, t, reverse_iterate (group_prerequisites (t)), m); - } - - // A target with an unspecified extension is considered equal to the one - // with the specified one. And when we find a target with an unspecified - // extension via a key with the specified one, we update the extension, - // essentially modifying the map's key. To make this work we use a hash - // map. The key's hash ignores the extension, so the hash will stay stable - // across extension updates. - // - // Note also that once the extension is specified, it becomes immutable. - // - class target_set - { - public: - using map_type = std::unordered_map>; - - // Return existing target or NULL. - // - const target* - find (const target_key& k, tracer& trace) const; - - const target* - find (const target_type& type, - const dir_path& dir, - const dir_path& out, - const string& name, - const optional& ext, - tracer& trace) const - { - return find (target_key {&type, &dir, &out, &name, ext}, trace); - } - - template - const T* - find (const target_type& type, - const dir_path& dir, - const dir_path& out, - const string& name, - const optional& ext, - tracer& trace) const - { - return static_cast (find (type, dir, out, name, ext, trace)); - } - - // As above but ignore the extension. - // - template - const T* - find (const dir_path& dir, const dir_path& out, const string& name) const - { - slock l (mutex_); - - auto i ( - map_.find ( - target_key {&T::static_type, &dir, &out, &name, nullopt})); - - return i != map_.end () - ? static_cast (i->second.get ()) - : nullptr; - } - - // If the target was inserted, keep the map exclusive-locked and return - // the lock. In this case, the target is effectively still being created - // since nobody can see it until the lock is released. - // - pair - insert_locked (const target_type&, - dir_path dir, - dir_path out, - string name, - optional ext, - bool implied, - tracer&); - - pair - insert (const target_type& tt, - dir_path dir, - dir_path out, - string name, - optional ext, - bool implied, - tracer& t) - { - auto p (insert_locked (tt, - move (dir), - move (out), - move (name), - move (ext), - implied, - t)); - - return pair (p.first, p.second.owns_lock ()); - } - - // Note that the following versions always enter implied targets. - // - template - T& - insert (const target_type& tt, - dir_path dir, - dir_path out, - string name, - optional ext, - tracer& t) - { - return insert (tt, - move (dir), - move (out), - move (name), - move (ext), - true, - t).first.template as (); - } - - template - T& - insert (const dir_path& dir, - const dir_path& out, - const string& name, - const optional& ext, - tracer& t) - { - return insert (T::static_type, dir, out, name, ext, t); - } - - template - T& - insert (const dir_path& dir, - const dir_path& out, - const string& name, - tracer& t) - { - return insert (dir, out, name, nullopt, t); - } - - // Note: not MT-safe so can only be used during serial execution. - // - public: - using iterator = butl::map_iterator_adapter; - - iterator begin () const {return map_.begin ();} - iterator end () const {return map_.end ();} - - void - clear () {map_.clear ();} - - private: - friend class target; // Access to mutex. - - mutable shared_mutex mutex_; - map_type map_; - }; - - extern target_set targets; - - // Modification time-based target. - // - class mtime_target: public target - { - public: - using target::target; - - // Modification time is an "atomic cash". That is, it can be set at any - // time and we assume everything will be ok regardless of the order in - // which racing updates happen because we do not modify the external state - // (which is the source of timestemps) while updating the internal. - // - // The rule for groups that utilize target_state::group is as follows: if - // it has any members that are mtime_targets, then the group should be - // mtime_target and the members get the mtime from it. - // - // Note that this function can be called before the target is matched in - // which case the value always comes from the target itself. In other - // words, that group logic only kicks in once the target is matched. - // - timestamp - mtime () const; - - // Note also that while we can cache the mtime, it may be ignored if the - // target state is set to group (see above). - // - void - mtime (timestamp) const; - - // If the mtime is unknown, then load it from the filesystem also caching - // the result. - // - // Note: can only be called during executing and must not be used if the - // target state is group. - // - timestamp - load_mtime (const path&) const; - - // Return true if this target is newer than the specified timestamp. - // - // Note: can only be called during execute on a synchronized target. - // - bool - newer (timestamp) const; - - public: - static const target_type static_type; - - protected: - // C++17: - // - // static_assert (atomic::is_always_lock_free, - // "timestamp is not lock-free on this architecture"); - -#if !defined(ATOMIC_LLONG_LOCK_FREE) || ATOMIC_LLONG_LOCK_FREE != 2 -# error timestamp is not lock-free on this architecture -#endif - - // Note that the value is not used to synchronize any other state so we - // use the release-consume ordering (i.e., we are only interested in the - // mtime value being synchronized). - // - // Store it as an underlying representation (normally int64_t) since - // timestamp is not usable with atomic (non-noexcept default ctor). - // - mutable atomic mtime_ {timestamp_unknown_rep}; - }; - - // Filesystem path-based target. - // - class path_target: public mtime_target - { - public: - using mtime_target::mtime_target; - - typedef build2::path path_type; - - // Target path is an "atomic consistent cash". That is, it can be set at - // any time but any subsequent updates must set the same path. Or, in - // other words, once the path is set, it never changes. - // - // A set empty path may signify special unknown/undetermined location (for - // example an installed import library -- we know it's there, just not - // exactly where). In this case you would also normally set its mtime. We - // used to return a pointer to properly distinguish between not set and - // empty but that proved too tedious. Note that this means there could be - // a race between path and mtime (unless you lock the target in some other - // way; see file_rule) so for this case it makes sense to set the - // timestamp first. - // - const path_type& - path () const; - - const path_type& - path (path_type) const; - - timestamp - load_mtime () const {return mtime_target::load_mtime (path ());} - - // Derive a path from target's dir, name, and, if set, ext. If ext is not - // set, try to derive it using the target type extension function and - // fallback to default_ext, if specified. In both cases also update the - // target's extension (this becomes important if later we need to reliably - // determine whether this file has an extension; think hxx{foo.bar.} and - // hxx{*}:extension is empty). - // - // If name_prefix is not NULL, add it before the name part and after the - // directory. Similarly, if name_suffix is not NULL, add it after the name - // part and before the extension. - // - // Finally, if the path was already assigned to this target, then this - // function verifies that the two are the same. - // - const path_type& - derive_path (const char* default_ext = nullptr, - const char* name_prefix = nullptr, - const char* name_suffix = nullptr); - - // This version can be used to derive the path from another target's path - // by adding another extension. - // - const path_type& - derive_path (path_type base, const char* default_ext = nullptr); - - // As above but only derives (and returns) the extension (empty means no - // extension used). If search is true then look for the extension as if - // it was a prerequisite, not a target. - // - const string& - derive_extension (const char* default_ext = nullptr, bool search = false); - - // Const versions of the above that can be used on unlocked targets. Note - // that here we don't allow providing any defaults since you probably - // should only use this version if everything comes from the target itself - // (and is therefore atomic). - // - const path_type& - derive_path () const - { - return const_cast (this)->derive_path (); // MT-aware. - } - - const string& - derive_extension () const - { - return const_cast (this)->derive_extension (); // MT-aware. - } - - public: - static const target_type static_type; - - private: - // Note that the state is also used to synchronize the path value so - // we use the release-acquire ordering. - // - // 0 - absent - // 1 - being set - // 2 - present - // - mutable atomic path_state_ {0}; - mutable path_type path_; - }; - - // File target. - // - class file: public path_target - { - public: - using path_target::path_target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Alias target. It represents a list of targets (its prerequisites) - // as a single "name". - // - class alias: public target - { - public: - using target::target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Directory target. Note that this is not a filesystem directory - // but rather an alias target with the directory name. For actual - // filesystem directory (creation), see fsdir. - // - class dir: public alias - { - public: - using alias::alias; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - - public: - template - static const target* - search_implied (const scope&, const K&, tracer&); - }; - - // While a filesystem directory is mtime-based, the semantics is - // not very useful in our case. In particular, if another target - // depends on fsdir{}, then all that's desired is the creation of - // the directory if it doesn't already exist. In particular, we - // don't want to update the target just because some unrelated - // entry was created in that directory. - // - class fsdir: public target - { - public: - using target::target; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Executable file. - // - class exe: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class buildfile: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // This is the venerable .in ("input") file that needs some kind of - // preprocessing. - // - // One interesting aspect of this target type is that the prerequisite - // search is target-dependent. Consider: - // - // hxx{version}: in{version.hxx} // version.hxx.in -> version.hxx - // - // Having to specify the header extension explicitly is inelegant. Instead - // what we really want to write is this: - // - // hxx{version}: in{version} - // - // But how do we know that in{version} means version.hxx.in? That's where - // the target-dependent search comes in: we take into account the target - // we are a prerequisite of. - // - class in: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Common documentation file targets. - // - // @@ Maybe these should be in the built-in doc module? - // - class doc: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // The problem with man pages is this: different platforms have - // different sets of sections. What seems to be the "sane" set - // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps - // 8 to 1M (system administration). The section determines two - // things: the directory where the page is installed (e.g., - // /usr/share/man/man1) as well as the extension of the file - // (e.g., test.1). Note also that there could be sub-sections, - // e.g., 1p (for POSIX). Such a page would still go into man1 - // but will have the .1p extension (at least that's what happens - // on Linux). The challenge is to somehow handle this in a - // portable manner. So here is the plan: - // - // First of all, we have the man{} target type which can be used - // for a custom man page. That is, you can have any extension and - // install it anywhere you please: - // - // man{foo.X}: install = man/manX - // - // Then we have man1..9{} target types which model the "sane" - // section set and that would be automatically installed into - // correct locations on other platforms. In other words, the - // idea is that you should be able to have the foo.8 file, - // write man8{foo} and have it installed as man1m/foo.1m on - // some SysV host. - // - // Re-mapping the installation directory is easy: to help with - // that we have assigned install.man1..9 directory names. The - // messy part is to change the extension. It seems the only - // way to do that would be to have special logic for man pages - // in the generic install rule. @@ This is still a TODO. - // - // Note that handling subsections with man1..9{} is easy, we - // simply specify the extension explicitly, e.g., man{foo.1p}. - // - class man: public doc - { - public: - using doc::doc; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - class man1: public man - { - public: - using man::man; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - - // Common implementation of the target factory, extension, and - // search functions. - // - template - pair> - target_factory (const target_type&, - dir_path d, - dir_path o, - string n, - optional e) - { - return make_pair (new T (move (d), move (o), move (n)), move (e)); - } - - // Return fixed target extension. - // - template - optional - target_extension_fix (const target_key&, const scope&, bool); - - template - bool - target_pattern_fix (const target_type&, const scope&, string&, bool); - - // Get the extension from the variable or use the default if none set. If - // the default is NULL, then return NULL. - // - template - optional - target_extension_var (const target_key&, const scope&, bool); - - template - bool - target_pattern_var (const target_type&, const scope&, string&, bool); - - // Always return NULL extension. - // - optional - target_extension_null (const target_key&, const scope&, bool); - - // Assert if called. - // - optional - target_extension_assert (const target_key&, const scope&, bool); - - // Target print functions. - // - - // Target type uses the extension but it is fixed and there is no use - // printing it (e.g., man1{}). - // - void - target_print_0_ext_verb (ostream&, const target_key&); - - // Target type uses the extension and there is normally no default so it - // should be printed (e.g., file{}). - // - void - target_print_1_ext_verb (ostream&, const target_key&); - - // The default behavior, that is, look for an existing target in the - // prerequisite's directory scope. - // - const target* - target_search (const target&, const prerequisite_key&); - - // First look for an existing target as above. If not found, then look - // for an existing file in the target-type-specific list of paths. - // - const target* - file_search (const target&, const prerequisite_key&); -} - -#include -#include - -#endif // BUILD2_TARGET diff --git a/build2/target-key b/build2/target-key deleted file mode 100644 index 084885f..0000000 --- a/build2/target-key +++ /dev/null @@ -1,96 +0,0 @@ -// file : build2/target-key -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TARGET_KEY -#define BUILD2_TARGET_KEY - -#include - -#include // compare_c_string - -#include -#include - -#include - -namespace build2 -{ - // Light-weight (by being shallow-pointing) target key. - // - class target_key - { - public: - const target_type* const type; - const dir_path* const dir; // Can be relative if part of prerequisite_key. - const dir_path* const out; // Can be relative if part of prerequisite_key. - const string* const name; - mutable optional ext; // Absent - unspecified, empty - none. - - template - bool is_a () const {return type->is_a ();} - bool is_a (const target_type& tt) const {return type->is_a (tt);} - - // The above references have to track the original objects so we cannot - // have assignment. - // - // @@ We could use references for all members, not just ext. - // - target_key (target_key&&) = default; - target_key (const target_key&) = default; - - target_key& operator= (target_key&&) = delete; - target_key& operator= (const target_key&) = delete; - }; - - inline bool - operator== (const target_key& x, const target_key& y) - { - // Unspecified and specified extension are assumed equal. - // - return - x.type == y.type && - *x.dir == *y.dir && - *x.out == *y.out && - *x.name == *y.name && - (!x.ext || !y.ext || *x.ext == *y.ext); - } - - inline bool - operator!= (const target_key& x, const target_key& y) {return !(x == y);} - - // If the target type has a custom print function, call that. Otherwise, - // call to_stream() with the current stream verbosity as a third argument. - // Both are defined in target.cxx. - // - ostream& - operator<< (ostream&, const target_key&); - - ostream& - to_stream (ostream&, const target_key&, uint16_t ext_verb); -} - -namespace std -{ - // Note that we ignore the extension when calculating the hash because of - // its special "unspecified" logic (see operator== above). - // - template <> - struct hash - { - using argument_type = build2::target_key; - using result_type = size_t; - - size_t - operator() (const build2::target_key& k) const noexcept - { - return build2::combine_hash ( - hash () (k.type), - hash () (*k.dir), - hash () (*k.out), - hash () (*k.name)); - } - }; -} - -#endif // BUILD2_TARGET_KEY diff --git a/build2/target-key.hxx b/build2/target-key.hxx new file mode 100644 index 0000000..0c93580 --- /dev/null +++ b/build2/target-key.hxx @@ -0,0 +1,96 @@ +// file : build2/target-key.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_KEY_HXX +#define BUILD2_TARGET_KEY_HXX + +#include + +#include // compare_c_string + +#include +#include + +#include + +namespace build2 +{ + // Light-weight (by being shallow-pointing) target key. + // + class target_key + { + public: + const target_type* const type; + const dir_path* const dir; // Can be relative if part of prerequisite_key. + const dir_path* const out; // Can be relative if part of prerequisite_key. + const string* const name; + mutable optional ext; // Absent - unspecified, empty - none. + + template + bool is_a () const {return type->is_a ();} + bool is_a (const target_type& tt) const {return type->is_a (tt);} + + // The above references have to track the original objects so we cannot + // have assignment. + // + // @@ We could use references for all members, not just ext. + // + target_key (target_key&&) = default; + target_key (const target_key&) = default; + + target_key& operator= (target_key&&) = delete; + target_key& operator= (const target_key&) = delete; + }; + + inline bool + operator== (const target_key& x, const target_key& y) + { + // Unspecified and specified extension are assumed equal. + // + return + x.type == y.type && + *x.dir == *y.dir && + *x.out == *y.out && + *x.name == *y.name && + (!x.ext || !y.ext || *x.ext == *y.ext); + } + + inline bool + operator!= (const target_key& x, const target_key& y) {return !(x == y);} + + // If the target type has a custom print function, call that. Otherwise, + // call to_stream() with the current stream verbosity as a third argument. + // Both are defined in target.cxx. + // + ostream& + operator<< (ostream&, const target_key&); + + ostream& + to_stream (ostream&, const target_key&, uint16_t ext_verb); +} + +namespace std +{ + // Note that we ignore the extension when calculating the hash because of + // its special "unspecified" logic (see operator== above). + // + template <> + struct hash + { + using argument_type = build2::target_key; + using result_type = size_t; + + size_t + operator() (const build2::target_key& k) const noexcept + { + return build2::combine_hash ( + hash () (k.type), + hash () (*k.dir), + hash () (*k.out), + hash () (*k.name)); + } + }; +} + +#endif // BUILD2_TARGET_KEY_HXX diff --git a/build2/target-type b/build2/target-type deleted file mode 100644 index aa2d7cb..0000000 --- a/build2/target-type +++ /dev/null @@ -1,138 +0,0 @@ -// file : build2/target-type -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TARGET_TYPE -#define BUILD2_TARGET_TYPE - -#include - -#include -#include - -namespace build2 -{ - class scope; - class target; - class target_key; - class prerequisite_key; - - // Target type. - // - // Note that we assume there is always a single instance of this class for - // any target type. As a result, we can use address comparison to determine - // if two target types are the same. - // - // If the extension derivation function is NULL, then it means this target - // type does not use extensions. Note that this is relied upon when deciding - // whether to print the extension; if the target does use extensions but the - // defaults could not (and its ok), could not (and its not ok), or should not - // (logically) be obtained, then use target_extension_{null,fail,assert}(), - // respectively. If the extension function returns NULL, then that means the - // default extension for this target could not be derived. - // - // The extension is used in two places: search_existing_file() (called for a - // prerequisite with the last argument true) and in target::derive_path() - // (called for a target with the last argument false); see their - // implementations for details. - // - // If the pattern function is not NULL, then it is used to amend a pattern - // or match (reverse is false) and then, if the amendment call returned - // true, to reverse it in the resulting matches. - // - struct target_type - { - const char* name; - const target_type* base; - - // Return target and extension. - // - pair> (*factory) (const target_type&, - dir_path, - dir_path, - string, - optional); - - optional (*extension) (const target_key&, - const scope&, - bool search); - - bool (*pattern) (const target_type&, const scope&, string&, bool reverse); - - void (*print) (ostream&, const target_key&); - - const target* (*search) (const target&, const prerequisite_key&); - - bool see_through; // A group with the default "see through" semantics. - - template - bool - is_a () const {return is_a (T::static_type);} - - bool - is_a (const target_type& tt) const - { - return this == &tt || (base != nullptr && is_a_base (tt)); - } - - bool - is_a_base (const target_type&) const; // Defined in target.cxx - }; - - inline bool - operator< (const target_type& x, const target_type& y) {return &x < &y;} - - inline bool - operator== (const target_type& x, const target_type& y) {return &x == &y;} - - inline bool - operator!= (const target_type& x, const target_type& y) {return &x != &y;} - - inline ostream& - operator<< (ostream& os, const target_type& tt) {return os << tt.name;} - - // Target type map. - // - struct target_type_ref - { - // Like reference_wrapper except it sometimes deletes the target type. - // - explicit - target_type_ref (const target_type& r): p_ (&r), d_ (false) {} - - explicit - target_type_ref (unique_ptr&& p) - : p_ (p.release ()), d_ (true) {} - - target_type_ref (target_type_ref&& r) - : p_ (r.p_), d_ (r.d_) {r.p_ = nullptr;} - - ~target_type_ref () {if (p_ != nullptr && d_) delete p_;} - - explicit operator const target_type& () const {return *p_;} - const target_type& get () const {return *p_;} - - private: - const target_type* p_; - bool d_; - }; - - using target_type_map_base = std::map; - - class target_type_map: public target_type_map_base - { - public: - const target_type& - insert (const target_type& tt) - { - emplace (tt.name, target_type_ref (tt)); - return tt; - } - - template - const target_type& - insert () {return insert (T::static_type);} - }; -} - -#endif // BUILD2_TARGET_TYPE diff --git a/build2/target-type.hxx b/build2/target-type.hxx new file mode 100644 index 0000000..0b8ee54 --- /dev/null +++ b/build2/target-type.hxx @@ -0,0 +1,138 @@ +// file : build2/target-type.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_TYPE_HXX +#define BUILD2_TARGET_TYPE_HXX + +#include + +#include +#include + +namespace build2 +{ + class scope; + class target; + class target_key; + class prerequisite_key; + + // Target type. + // + // Note that we assume there is always a single instance of this class for + // any target type. As a result, we can use address comparison to determine + // if two target types are the same. + // + // If the extension derivation function is NULL, then it means this target + // type does not use extensions. Note that this is relied upon when deciding + // whether to print the extension; if the target does use extensions but the + // defaults could not (and its ok), could not (and its not ok), or should not + // (logically) be obtained, then use target_extension_{null,fail,assert}(), + // respectively. If the extension function returns NULL, then that means the + // default extension for this target could not be derived. + // + // The extension is used in two places: search_existing_file() (called for a + // prerequisite with the last argument true) and in target::derive_path() + // (called for a target with the last argument false); see their + // implementations for details. + // + // If the pattern function is not NULL, then it is used to amend a pattern + // or match (reverse is false) and then, if the amendment call returned + // true, to reverse it in the resulting matches. + // + struct target_type + { + const char* name; + const target_type* base; + + // Return target and extension. + // + pair> (*factory) (const target_type&, + dir_path, + dir_path, + string, + optional); + + optional (*extension) (const target_key&, + const scope&, + bool search); + + bool (*pattern) (const target_type&, const scope&, string&, bool reverse); + + void (*print) (ostream&, const target_key&); + + const target* (*search) (const target&, const prerequisite_key&); + + bool see_through; // A group with the default "see through" semantics. + + template + bool + is_a () const {return is_a (T::static_type);} + + bool + is_a (const target_type& tt) const + { + return this == &tt || (base != nullptr && is_a_base (tt)); + } + + bool + is_a_base (const target_type&) const; // Defined in target.cxx + }; + + inline bool + operator< (const target_type& x, const target_type& y) {return &x < &y;} + + inline bool + operator== (const target_type& x, const target_type& y) {return &x == &y;} + + inline bool + operator!= (const target_type& x, const target_type& y) {return &x != &y;} + + inline ostream& + operator<< (ostream& os, const target_type& tt) {return os << tt.name;} + + // Target type map. + // + struct target_type_ref + { + // Like reference_wrapper except it sometimes deletes the target type. + // + explicit + target_type_ref (const target_type& r): p_ (&r), d_ (false) {} + + explicit + target_type_ref (unique_ptr&& p) + : p_ (p.release ()), d_ (true) {} + + target_type_ref (target_type_ref&& r) + : p_ (r.p_), d_ (r.d_) {r.p_ = nullptr;} + + ~target_type_ref () {if (p_ != nullptr && d_) delete p_;} + + explicit operator const target_type& () const {return *p_;} + const target_type& get () const {return *p_;} + + private: + const target_type* p_; + bool d_; + }; + + using target_type_map_base = std::map; + + class target_type_map: public target_type_map_base + { + public: + const target_type& + insert (const target_type& tt) + { + emplace (tt.name, target_type_ref (tt)); + return tt; + } + + template + const target_type& + insert () {return insert (T::static_type);} + }; +} + +#endif // BUILD2_TARGET_TYPE_HXX diff --git a/build2/target.cxx b/build2/target.cxx index f7500f7..1b6757c 100644 --- a/build2/target.cxx +++ b/build2/target.cxx @@ -2,16 +2,16 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // file_mtime() +#include // file_mtime() -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/target.hxx b/build2/target.hxx new file mode 100644 index 0000000..884b185 --- /dev/null +++ b/build2/target.hxx @@ -0,0 +1,1719 @@ +// file : build2/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TARGET_HXX +#define BUILD2_TARGET_HXX + +#include // tags, etc. +#include // aligned_storage +#include + +#include // map_iterator_adapter + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + class rule; + class scope; + class target; + + extern size_t current_on; // From . + + const target& + search (const target&, const prerequisite&); // From . + + // Target state. + // + enum class target_state: uint8_t + { + // The order of the enumerators is arranged so that their integral values + // indicate whether one "overrides" the other in the "merge" operator| + // (see below). + // + // Note that postponed is "greater" than unchanged since it may result in + // the changed state. + // + unknown, + unchanged, + postponed, + busy, + changed, + failed, + group // Target's state is the group's state. + }; + + ostream& + operator<< (ostream&, target_state); + + inline target_state& + operator |= (target_state& l, target_state r) + { + if (static_cast (r) > static_cast (l)) + l = r; + + return l; + } + + // Recipe. + // + // The returned target state is normally changed or unchanged. If there is + // an error, then the recipe should throw failed rather than returning (this + // is the only exception that a recipe can throw). + // + // The return value of the recipe is used to update the target state. If it + // is target_state::group then the target's state is the group's state. + // + // The recipe may also return postponed in which case the target state is + // assumed to be unchanged (normally this means a prerequisite was postponed + // and while the prerequisite will be re-examined via another dependency, + // this target is done). + // + // Note that max size for the "small capture optimization" in std::function + // ranges (in pointer sizes) from 0 (GCC prior to 5) to 2 (GCC 5) to 6 (VC + // 14u2). With the size ranging (in bytes for 64-bit target) from 32 (GCC) + // to 64 (VC). + // + using recipe_function = target_state (action, const target&); + using recipe = function; + + // Commonly-used recipes. The default recipe executes the action on + // all the prerequisites in a loop, skipping ignored. Specifically, + // for actions with the "first" execution mode, it calls + // execute_prerequisites() while for those with the "last" mode -- + // reverse_execute_prerequisites(); see , + // for details. The group recipe call's the group's + // recipe. + // + extern const recipe empty_recipe; + extern const recipe noop_recipe; + extern const recipe default_recipe; + extern const recipe group_recipe; + + target_state + noop_action (action, const target&); // Defined in . + + target_state + group_action (action, const target&); // Defined in . + + // A view of target group members. + // + struct group_view + { + const target* const* members; // NULL means not yet known. + size_t count; + }; + + // Target. + // + class target + { + optional* ext_; // Reference to value in target_key. + + public: + // For targets that are in the src tree of a project we also keep the + // corresponding out directory. As a result we may end up with multiple + // targets for the same file if we are building multiple configurations of + // the same project at once. We do it this way because, in a sense, a + // target's out directory is its "configuration" (in terms of variables). + // As an example, consider installing the same README file (src) but for + // two different project configurations at once. Which installation + // directory should we use? The answer depends on which configuration you + // ask. + // + // Empty out directory indicates this target is in the out tree (including + // when src == out). We also treat out of project targets as being in the + // out tree. + // + const dir_path dir; // Absolute and normalized. + const dir_path out; // Empty or absolute and normalized. + const string name; + + const string* ext () const; // Return NULL if not specified. + const string& ext (string); + + const dir_path& + out_dir () const {return out.empty () ? dir : out;} + + // Target group to which this target belongs, if any. Note that we assume + // that the group and all its members are in the same scope (for example, + // in variable lookup). We also don't support nested groups (with a small + // exception for ad hoc groups; see below). + // + // The semantics of the interaction between the group and its members and + // what it means to, say, update the group, is unspecified and is + // determined by the group's type. In particular, a group can be created + // out of member types that have no idea they are part of this group + // (e.g., cli.cxx{}). + // + // Normally, however, there are two kinds of groups: "alternatives" and + // "combination". In an alternatives group, normally one of the members is + // selected when the group is mentioned as a prerequisite with, perhaps, + // an exception for special rules, like aliases, where it makes more sense + // to treat the group as a whole. In this case we say that the rule + // "semantically recognizes" the group and picks some of its members. + // + // Updating an alternatives group as a whole can mean updating some subset + // of its members (e.g., lib{}). Or the group may not support this at all + // (e.g., obj{}). + // + // In a combination group, when a group is updated, normally all members + // are updates (and usually with a single command), though there could be + // some members that are omitted, depending on the configuration (e.g., an + // inline file not/being generated). When a combination group is mentioned + // as a prerequisite, the rule is usually interested in the individual + // members rather than the whole group. For example, a C++ compile rule + // would like to "see" the ?xx{} members when it gets a cli.cxx{} group. + // + // Which brings us to the group iteration mode. The target type contains a + // member called see_through that indicates whether the default iteration + // mode for the group should be "see through"; that is, whether we see the + // members or the group itself. For the iteration support itself, see the + // *_prerequisite_members() machinery below. + // + // In a combination group we usually want the state (and timestamp; see + // mtime()) for members to come from the group. This is achieved with the + // special target_state::group state. You would normally also use the + // group_recipe for group members. + // + const target* group = nullptr; + + + // What has been described above is a "normal" group. That is, there is + // a dedicated target type that explicitly serves as a group and there + // is an explicit mechanism for discovering the group's members. + // + // However, sometimes, we may want to create a group on the fly out of a + // normal target type. For example, we have the libs{} target type. But + // on Windows a shared library consist of (at least) two files: the import + // library and the DLL itself. So we somehow need to be able to capture + // that. One approach would be to imply the presence of the second file. + // However, that means that a lot of generic rules (e.g., clean, install, + // etc) will need to know about this special semantics on Windows. Also, + // there would be no convenient way to customize things like extensions, + // etc (for which we use target-specific variables). In other words, it + // would be much easier and more consistent to make these extra files + // proper targets. + // + // So to support this requirement we have "ad hoc" groups. The idea is + // that any target can be turned (by the rule that matched it) into an ad + // hoc group by chaining several targets. Ad hoc groups have a more + // restricted semantics compared to the normal groups. In particular: + // + // - The ad hoc group itself is in a sense its first/primary target. + // + // - Group member's recipes should be set to group_recipe by the group's + // rule. + // + // - Members are discovered lazily, they are only known after the group's + // rule's apply() call. + // + // - Members cannot be used as prerequisites but can be used as targets + // - (e.g., to set variables, etc). + // + // - Members don't have prerequisites. + // + // - Ad hoc group cannot have sub group (of any kind) though an ad hoc + // group can be a sub-group of a normal group. + // + // - Member variable lookup skips the ad hoc group (since the group is + // the first member, this is normally what we want). + // + const_ptr member = nullptr; + + bool + adhoc_group () const + { + // An ad hoc group can be a member of a normal group. + // + return member != nullptr && + (group == nullptr || group->member == nullptr); + } + + bool + adhoc_member () const + { + return group != nullptr && group->member != nullptr; + } + + public: + using action_type = build2::action; + + // You should not call this function directly; rather use + // resolve_group_members() from . + // + virtual group_view + group_members (action_type) const; + + // Note that the returned key "tracks" the target (except for the + // extension). + // + target_key + key () const; + + // Scoping. + // + public: + // Most qualified scope that contains this target. + // + const scope& + base_scope () const; + + // Root scope of a project that contains this target. Note that + // a target can be out of any (known) project root in which case + // this function asserts. If you need to detect this situation, + // then use base_scope().root_scope() expression instead. + // + const scope& + root_scope () const; + + // Root scope of a strong amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + const scope& + strong_scope () const {return *root_scope ().strong_scope ();} + + // Root scope of the outermost amalgamation that contains this target. + // The same notes as to root_scope() apply. + // + const scope& + weak_scope () const {return *root_scope ().weak_scope ();} + + bool + in (const scope& s) const + { + return out_dir ().sub (s.out_path ()); + } + + // Prerequisites. + // + // We use an atomic-empty semantics that allows one to "swap in" a set of + // prerequisites if none were specified. This is used to implement + // "synthesized" dependencies. + // + public: + using prerequisites_type = build2::prerequisites; + + const prerequisites_type& + prerequisites () const; + + // Swap-in a list of prerequisites. Return false if unsuccessful (i.e., + // someone beat us to it). Note that it can be called on const target. + // + bool + prerequisites (prerequisites_type&&) const; + + // Check if there are any prerequisites, taking into account group + // prerequisites. + // + bool + has_prerequisites () const + { + return !prerequisites ().empty () || + (group != nullptr && !group->prerequisites ().empty ()); + } + + private: + friend class parser; + + // Note that the state is also used to synchronize the prerequisites + // value so we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + atomic prerequisites_state_ {0}; + prerequisites_type prerequisites_; + + static const prerequisites_type empty_prerequisites_; + + // Target-specific variables. + // + public: + variable_map vars; + + // Lookup, including in groups to which this target belongs and then in + // outer scopes (including target type/pattern-specific variables). If you + // only want to lookup in this target, do it on the variable map directly + // (and note that there will be no overrides). + // + lookup + operator[] (const variable& var) const + { + return find (var).first; + } + + lookup + operator[] (const variable* var) const // For cached variables. + { + assert (var != nullptr); + return operator[] (*var); + } + + lookup + operator[] (const string& name) const + { + const variable* var (var_pool.find (name)); + return var != nullptr ? operator[] (*var) : lookup (); + } + + // As above but also return the depth at which the value is found. The + // depth is calculated by adding 1 for each test performed. So a value + // that is from the target will have depth 1. That from the group -- 2. + // From the innermost scope's target type/patter-specific variables -- + // 3. From the innermost scope's variables -- 4. And so on. The idea is + // that given two lookups from the same target, we can say which one came + // earlier. If no value is found, then the depth is set to ~0. + // + // + pair + find (const variable& var) const + { + auto p (find_original (var)); + return var.override == nullptr + ? p + : base_scope ().find_override (var, move (p), true); + } + + // If target_only is true, then only look in target and its target group + // without continuing in scopes. + // + pair + find_original (const variable&, bool target_only = false) const; + + // Return a value suitable for assignment. See scope for details. + // + value& + assign (const variable& var) {return vars.assign (var);} + + // Return a value suitable for appending. See class scope for details. + // + value& + append (const variable&); + + // A target that is not (yet) entered as part of a real dependency + // declaration (for example, that is entered as part of a target-specific + // variable assignment, dependency extraction, etc) is called implied. + // + // The implied flag should only be cleared during the load phase via the + // MT-safe target_set::insert(). + // + public: + bool implied; + + // Target state. + // + public: + // Atomic task count that is used during match and execution to track the + // target's "meta-state" as well as the number of its sub-tasks (e.g., + // busy+1, busy+2, and so on, for instance, number of prerequisites + // being matched or executed). + // + // For each operation in a meta-operation batch (current_on) we have a + // "band" of counts, [touched, executed], that represent the target + // meta-state. Once the next operation is started, this band "moves" thus + // automatically resetting the target to "not yet touched" state for this + // operation. + // + // For match we have a further complication in that we may re-match the + // target and override with a "stronger" recipe thus re-setting the state + // from, say, applied back to touched. + // + // The target is said to be synchronized (in this thread) if we have + // either observed the task count to reach applied or executed or we have + // successfully changed it (via compare_exchange) to locked or busy. If + // the target is synchronized, then we can access and modify (second case) + // its state etc. + // + static const size_t offset_touched = 1; // Has been locked. + static const size_t offset_matched = 2; // Rule has been matched. + static const size_t offset_applied = 3; // Rule has been applied. + static const size_t offset_executed = 4; // Recipe has been executed. + static const size_t offset_locked = 5; // Fast (spin) lock. + static const size_t offset_busy = 6; // Slow (wait) lock. + + static size_t count_base () {return 4 * (current_on - 1);} + + static size_t count_touched () {return offset_touched + count_base ();} + static size_t count_matched () {return offset_matched + count_base ();} + static size_t count_applied () {return offset_applied + count_base ();} + static size_t count_executed () {return offset_executed + count_base ();} + static size_t count_locked () {return offset_locked + count_base ();} + static size_t count_busy () {return offset_busy + count_base ();} + + mutable atomic_count task_count {0}; // Start offset_touched - 1. + + // This function should only be called during match if we have observed + // (synchronization-wise) that the this target has been matched (i.e., + // the rule has been applied) for this action. + // + target_state + matched_state (action a, bool fail = true) const; + + // This function should only be called during execution if we have + // observed (synchronization-wise) that this target has been executed. + // + target_state + executed_state (bool fail = true) const; + + // This function should only be called between match and execute while + // running serially. It returns the group state for the "final" action + // that has been matched (and that will be executed). + // + target_state + serial_state (bool fail = true) const; + + // Number of direct targets that depend on this target in the current + // operation. It is incremented during match and then decremented during + // execution, before running the recipe. As a result, the recipe can + // detect the last chance (i.e., last dependent) to execute the command + // (see also the first/last execution modes in ). + // + mutable atomic_count dependents; + + protected: + // Return fail-untranslated (but group-translated) state assuming the + // target is executed and synchronized. + // + target_state + state () const; + + // Version that should be used during match after the target has been + // matched for this action (see the recipe override). + // + target_state + state (action a) const; + + // Return true if the state comes from the group. Target must be at least + // matched. + // + bool + group_state () const; + + // Raw state, normally not accessed directly. + // + public: + target_state state_ = target_state::unknown; + + // Recipe. + // + public: + using recipe_type = build2::recipe; + using rule_type = build2::rule; + + action_type action; // Action the rule/recipe is for. + + // Matched rule (pointer to hint_rule_map element). Note that in case of a + // direct recipe assignment we may not have a rule. + // + const pair>* rule; + + // Applied recipe. + // + recipe_type recipe_; + + // Note that the target must be locked in order to set the recipe. + // + void + recipe (recipe_type); + + // After the target has been matched and synchronized, check if the target + // is known to be unchanged. Used for optimizations during search & match. + // + bool + unchanged (action_type a) const + { + return state (a) == target_state::unchanged; + } + + // Targets to which prerequisites resolve for this recipe. Note that + // unlike prerequisite::target, these can be resolved to group members. + // NULL means the target should be skipped (or the rule may simply not add + // such a target to the list). + // + // Note also that it is possible the target can vary from action to + // action, just like recipes. We don't need to keep track of the action + // here since the targets will be updated if the recipe is updated, + // normally as part of rule::apply(). + // + // Note that the recipe may modify this list. + // + using prerequisite_targets_type = vector; + mutable prerequisite_targets_type prerequisite_targets; + + // Auxilary data storage. + // + // A rule that matches (i.e., returns true from its match() function) may + // use this pad to pass data between its match and apply functions as well + // as the recipe. After the recipe is executed, the data is destroyed by + // calling data_dtor (if not NULL). The rule should static assert that the + // size of the pad is sufficient for its needs. + // + // Note also that normally at least 2 extra pointers may be stored without + // a dynamic allocation in the returned recipe (small object optimization + // in std::function). So if you need to pass data only between apply() and + // the recipe, then this might be a more convenient way. + // + // Note also that a rule that delegates to another rule may not be able to + // use this mechanism fully since the delegated-to rule may also need the + // data pad. + // + // Currenly the data is not destroyed until the next match. + // + // Note that the recipe may modify the data. + // + static constexpr size_t data_size = sizeof (string) * 10; + mutable std::aligned_storage::type data_pad; + + mutable void (*data_dtor) (void*) = nullptr; + + template ::type>::type> + typename std::enable_if::value,T&>::type + data (R&& d) const + { + assert (sizeof (T) <= data_size && data_dtor == nullptr); + return *new (&data_pad) T (forward (d)); + } + + template ::type>::type> + typename std::enable_if::value,T&>::type + data (R&& d) const + { + assert (sizeof (T) <= data_size && data_dtor == nullptr); + T& r (*new (&data_pad) T (forward (d))); + data_dtor = [] (void* p) {static_cast (p)->~T ();}; + return r; + } + + template + T& + data () const {return *reinterpret_cast (&data_pad);} + + void + clear_data () const + { + if (data_dtor != nullptr) + { + data_dtor (&data_pad); + data_dtor = nullptr; + } + } + + // Target type info and casting. + // + public: + bool + is_a (const target_type& tt) const {return type ().is_a (tt);} + + template + T* + is_a () {return dynamic_cast (this);} + + template + const T* + is_a () const {return dynamic_cast (this);} + + // Unchecked cast. + // + template + T& + as () {return static_cast (*this);} + + template + const T& + as () const {return static_cast (*this);} + + // Dynamic derivation to support define. + // + const target_type* derived_type = nullptr; + + const target_type& + type () const + { + return derived_type != nullptr ? *derived_type : dynamic_type (); + } + + virtual const target_type& dynamic_type () const = 0; + static const target_type static_type; + + public: + virtual + ~target (); + + target (const target&) = delete; + target& operator= (const target&) = delete; + + // The only way to create a target should be via the targets set below. + // + public: + friend class target_set; + + target (dir_path d, dir_path o, string n) + : dir (move (d)), out (move (o)), name (move (n)), + vars (false) // Note: not global. + {} + }; + + // All targets are from the targets set below. + // + inline bool + operator== (const target& x, const target& y) {return &x == &y;} + + inline bool + operator!= (const target& x, const target& y) {return !(x == y);} + + inline ostream& + operator<< (ostream& os, const target& t) {return os << t.key ();} + + // Sometimes it is handy to "mark" a pointer to a target (for example, in + // prerequisite_targets). We use the last 2 bits in a pointer for that (aka + // the "bit stealing" technique). Note that the pointer needs to be unmarked + // before it can be usable so care must be taken in the face of exceptions, + // etc. + // + void + mark (const target*&, uint8_t = 1); + + uint8_t + unmark (const target*&); + + // A "range" that presents the prerequisites of a group and one of + // its members as one continuous sequence, or, in other words, as + // if they were in a single container. The group's prerequisites + // come first followed by the member's. If you need to see them + // in the other direction, iterate in reverse, for example: + // + // for (prerequisite& p: group_prerequisites (t)) + // + // for (prerequisite& p: reverse_iterate (group_prerequisites (t)) + // + // Note that in this case the individual elements of each list will + // also be traversed in reverse, but that's what you usually want, + // anyway. + // + // For constant iteration use const_group_prerequisites(). + // + class group_prerequisites + { + public: + explicit + group_prerequisites (const target& t) + : t_ (t), + g_ (t_.group == nullptr || + t_.group->member != nullptr || // Ad hoc group member. + t_.group->prerequisites ().empty () + ? nullptr : t_.group) {} + + using prerequisites_type = target::prerequisites_type; + using base_iterator = prerequisites_type::const_iterator; + + struct iterator + { + using value_type = base_iterator::value_type; + using pointer = base_iterator::pointer; + using reference = base_iterator::reference; + using difference_type = base_iterator::difference_type; + using iterator_category = std::bidirectional_iterator_tag; + + iterator () {} + iterator (const target* t, + const target* g, + const prerequisites_type* c, + base_iterator i): t_ (t), g_ (g), c_ (c), i_ (i) {} + + iterator& + operator++ () + { + if (++i_ == c_->end () && c_ != &t_->prerequisites ()) + { + c_ = &t_->prerequisites (); + i_ = c_->begin (); + } + return *this; + } + + iterator + operator++ (int) {iterator r (*this); operator++ (); return r;} + + iterator& + operator-- () + { + if (i_ == c_->begin () && c_ == &t_->prerequisites ()) + { + c_ = &g_->prerequisites (); + i_ = c_->end (); + } + + --i_; + return *this; + } + + iterator + operator-- (int) {iterator r (*this); operator-- (); return r;} + + reference operator* () const {return *i_;} + pointer operator-> () const {return i_.operator -> ();} + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.t_ == y.t_ && x.g_ == y.g_ && x.c_ == y.c_ && x.i_ == y.i_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + private: + const target* t_ = nullptr; + const target* g_ = nullptr; + const prerequisites_type* c_ = nullptr; + base_iterator i_; + }; + + using reverse_iterator = std::reverse_iterator; + + iterator + begin () const + { + auto& c ((g_ != nullptr ? *g_ : t_).prerequisites ()); + return iterator (&t_, g_, &c, c.begin ()); + } + + iterator + end () const + { + auto& c (t_.prerequisites ()); + return iterator (&t_, g_, &c, c.end ()); + } + + reverse_iterator + rbegin () const {return reverse_iterator (end ());} + + reverse_iterator + rend () const {return reverse_iterator (begin ());} + + size_t + size () const + { + return t_.prerequisites ().size () + + (g_ != nullptr ? g_->prerequisites ().size () : 0); + } + + private: + const target& t_; + const target* g_; + }; + + // A member of a prerequisite. If 'target' is NULL, then this is the + // prerequisite itself. Otherwise, it is its member. In this case + // 'prerequisite' still refers to the prerequisite. + // + struct prerequisite_member + { + using scope_type = build2::scope; + using target_type = build2::target; + using prerequisite_type = build2::prerequisite; + using target_type_type = build2::target_type; + + const prerequisite_type& prerequisite; + const target_type* target; + + template + bool + is_a () const + { + return target != nullptr + ? target->is_a () != nullptr + : prerequisite.is_a (); + } + + bool + is_a (const target_type_type& tt) const + { + return target != nullptr ? target->is_a (tt) : prerequisite.is_a (tt); + } + + prerequisite_key + key () const + { + return target != nullptr + ? prerequisite_key {prerequisite.proj, target->key (), nullptr} + : prerequisite.key (); + } + + const target_type_type& + type () const + { + return target != nullptr ? target->type () : prerequisite.type; + } + + const string& + name () const + { + return target != nullptr ? target->name : prerequisite.name; + } + + const dir_path& + dir () const + { + return target != nullptr ? target->dir : prerequisite.dir; + } + + const optional& + proj () const + { + // Target cannot be project-qualified. + // + return target != nullptr + ? prerequisite_key::nullproj + : prerequisite.proj; + } + + const scope_type& + scope () const + { + return target != nullptr ? target->base_scope () : prerequisite.scope; + } + + const target_type& + search (const target_type& t) const + { + return target != nullptr ? *target : build2::search (t, prerequisite); + } + + // Return as a new prerequisite instance. + // + prerequisite_type + as_prerequisite () const; + }; + + // It is often stored as the target's auxiliary data so make sure there is + // no destructor overhead. + // + static_assert (std::is_trivially_destructible::value, + "prerequisite_member is not trivially destructible"); + + inline ostream& + operator<< (ostream& os, const prerequisite_member& pm) + { + return os << pm.key (); + } + + // A "range" that presents a sequence of prerequisites (e.g., from + // group_prerequisites()) as a sequence of prerequisite_member's. For each + // group prerequisite you will "see" either the prerequisite itself or all + // its members, depending on the default iteration mode of the target group + // type (ad hoc groups are always see through). You can skip the rest of the + // group members with leave_group() and you can force iteration over the + // members with enter_group(). Usage: + // + // for (prerequisite_member pm: prerequisite_members (a, ...)) + // + // Where ... can be: + // + // t.prerequisites + // reverse_iterate(t.prerequisites) + // group_prerequisites (t) + // reverse_iterate (group_prerequisites (t)) + // + // But use shortcuts instead: + // + // prerequisite_members (a, t) + // reverse_prerequisite_members (a, t) + // group_prerequisite_members (a, t) + // reverse_group_prerequisite_members (a, t) + // + template + class prerequisite_members_range; + + // See-through/ad hoc group members iteration mode. Unless the mode is never, + // ad hoc members are always iterated over. + // + enum class members_mode + { + always, // Iterate over members, assert if not resolvable. + maybe, // Iterate over members if resolvable, group otherwise. + never // Iterate over group (can still use enter_group()). + }; + + template + inline prerequisite_members_range + prerequisite_members (action a, const target& t, + R&& r, + members_mode m = members_mode::always) + { + return prerequisite_members_range (a, t, forward (r), m); + } + + template + class prerequisite_members_range + { + public: + prerequisite_members_range (action a, const target& t, + R&& r, + members_mode m) + : a_ (a), t_ (t), mode_ (m), r_ (forward (r)), e_ (r_.end ()) {} + + using base_iterator = decltype (declval ().begin ()); + + struct iterator + { + using value_type = prerequisite_member; + using pointer = const value_type*; + using reference = const value_type&; + using difference_type = typename base_iterator::difference_type; + using iterator_category = std::forward_iterator_tag; + + iterator (): r_ (nullptr) {} + iterator (const prerequisite_members_range* r, const base_iterator& i) + : r_ (r), i_ (i), g_ {nullptr, 0}, k_ (nullptr) + { + if (r_->mode_ != members_mode::never && + i_ != r_->e_ && + i_->type.see_through) + switch_mode (); + } + + iterator& operator++ (); + iterator operator++ (int) {iterator r (*this); operator++ (); return r;} + + // Skip iterating over the rest of this group's members, if any. Note + // that the only valid operation after this call is to increment the + // iterator. Note that it can be used on ad hoc groups. + // + void + leave_group (); + + // Iterate over this group's members. Return false if the member + // information is not available. Similar to leave_group(), you should + // increment the iterator after calling this function (provided it + // returned true). Note that it cannot be used on ad hoc groups (which + // will be always entered). + // + bool + enter_group (); + + value_type operator* () const + { + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return value_type {*i_, t}; + } + + pointer operator-> () const + { + static_assert ( + std::is_trivially_destructible::value, + "prerequisite_member is not trivially destructible"); + + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); + + return new (&m_) value_type {*i_, t}; + } + + friend bool + operator== (const iterator& x, const iterator& y) + { + return x.i_ == y.i_ && + x.g_.count == y.g_.count && + (x.g_.count == 0 || x.j_ == y.j_) && + x.k_ == y.k_; + } + + friend bool + operator!= (const iterator& x, const iterator& y) {return !(x == y);} + + // What we have here is a state for three nested iteration modes (and + // no, I am not proud of it). The innermost mode is iteration over an ad + // hoc group (k_). Then we have iteration over a normal group (g_ and + // j_). Finally, at the outer level, we have the range itself (i_). + // + // The ad hoc iteration is peculiar in that we only switch to this mode + // once the caller tries to increment past the group itself (which is + // the primary/first member). The reason for this is that the members + // will normally only be known once the caller searched and matched + // the group. + // + // Also, the enter/leave group support is full of ugly, special cases. + // + private: + void + switch_mode (); + + private: + const prerequisite_members_range* r_; + base_iterator i_; + group_view g_; + size_t j_; // 1-based index, to support enter_group(). + const target* k_; // Current member of ad hoc group or NULL. + mutable typename std::aligned_storage::type m_; + }; + + iterator + begin () const {return iterator (this, r_.begin ());} + + iterator + end () const {return iterator (this, e_);} + + private: + action a_; + const target& t_; + members_mode mode_; + R r_; + base_iterator e_; + }; + + // prerequisite_members(t.prerequisites) + // + inline auto + prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, t.prerequisites (), m); + } + + inline auto + prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, t.prerequisites (), m); + } + + // prerequisite_members(reverse_iterate(t.prerequisites)) + // + inline auto + reverse_prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, reverse_iterate (t.prerequisites ()), m); + } + + inline auto + reverse_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, reverse_iterate (t.prerequisites ()), m); + } + + // prerequisite_members(group_prerequisites (t)) + // + inline auto + group_prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, group_prerequisites (t), m); + } + + inline auto + group_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members (a, t, group_prerequisites (t), m); + } + + // prerequisite_members(reverse_iterate (group_prerequisites (t))) + // + inline auto + reverse_group_prerequisite_members (action a, target& t, + members_mode m = members_mode::always) + { + return prerequisite_members ( + a, t, reverse_iterate (group_prerequisites (t)), m); + } + + inline auto + reverse_group_prerequisite_members (action a, const target& t, + members_mode m = members_mode::always) + { + return prerequisite_members ( + a, t, reverse_iterate (group_prerequisites (t)), m); + } + + // A target with an unspecified extension is considered equal to the one + // with the specified one. And when we find a target with an unspecified + // extension via a key with the specified one, we update the extension, + // essentially modifying the map's key. To make this work we use a hash + // map. The key's hash ignores the extension, so the hash will stay stable + // across extension updates. + // + // Note also that once the extension is specified, it becomes immutable. + // + class target_set + { + public: + using map_type = std::unordered_map>; + + // Return existing target or NULL. + // + const target* + find (const target_key& k, tracer& trace) const; + + const target* + find (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const optional& ext, + tracer& trace) const + { + return find (target_key {&type, &dir, &out, &name, ext}, trace); + } + + template + const T* + find (const target_type& type, + const dir_path& dir, + const dir_path& out, + const string& name, + const optional& ext, + tracer& trace) const + { + return static_cast (find (type, dir, out, name, ext, trace)); + } + + // As above but ignore the extension. + // + template + const T* + find (const dir_path& dir, const dir_path& out, const string& name) const + { + slock l (mutex_); + + auto i ( + map_.find ( + target_key {&T::static_type, &dir, &out, &name, nullopt})); + + return i != map_.end () + ? static_cast (i->second.get ()) + : nullptr; + } + + // If the target was inserted, keep the map exclusive-locked and return + // the lock. In this case, the target is effectively still being created + // since nobody can see it until the lock is released. + // + pair + insert_locked (const target_type&, + dir_path dir, + dir_path out, + string name, + optional ext, + bool implied, + tracer&); + + pair + insert (const target_type& tt, + dir_path dir, + dir_path out, + string name, + optional ext, + bool implied, + tracer& t) + { + auto p (insert_locked (tt, + move (dir), + move (out), + move (name), + move (ext), + implied, + t)); + + return pair (p.first, p.second.owns_lock ()); + } + + // Note that the following versions always enter implied targets. + // + template + T& + insert (const target_type& tt, + dir_path dir, + dir_path out, + string name, + optional ext, + tracer& t) + { + return insert (tt, + move (dir), + move (out), + move (name), + move (ext), + true, + t).first.template as (); + } + + template + T& + insert (const dir_path& dir, + const dir_path& out, + const string& name, + const optional& ext, + tracer& t) + { + return insert (T::static_type, dir, out, name, ext, t); + } + + template + T& + insert (const dir_path& dir, + const dir_path& out, + const string& name, + tracer& t) + { + return insert (dir, out, name, nullopt, t); + } + + // Note: not MT-safe so can only be used during serial execution. + // + public: + using iterator = butl::map_iterator_adapter; + + iterator begin () const {return map_.begin ();} + iterator end () const {return map_.end ();} + + void + clear () {map_.clear ();} + + private: + friend class target; // Access to mutex. + + mutable shared_mutex mutex_; + map_type map_; + }; + + extern target_set targets; + + // Modification time-based target. + // + class mtime_target: public target + { + public: + using target::target; + + // Modification time is an "atomic cash". That is, it can be set at any + // time and we assume everything will be ok regardless of the order in + // which racing updates happen because we do not modify the external state + // (which is the source of timestemps) while updating the internal. + // + // The rule for groups that utilize target_state::group is as follows: if + // it has any members that are mtime_targets, then the group should be + // mtime_target and the members get the mtime from it. + // + // Note that this function can be called before the target is matched in + // which case the value always comes from the target itself. In other + // words, that group logic only kicks in once the target is matched. + // + timestamp + mtime () const; + + // Note also that while we can cache the mtime, it may be ignored if the + // target state is set to group (see above). + // + void + mtime (timestamp) const; + + // If the mtime is unknown, then load it from the filesystem also caching + // the result. + // + // Note: can only be called during executing and must not be used if the + // target state is group. + // + timestamp + load_mtime (const path&) const; + + // Return true if this target is newer than the specified timestamp. + // + // Note: can only be called during execute on a synchronized target. + // + bool + newer (timestamp) const; + + public: + static const target_type static_type; + + protected: + // C++17: + // + // static_assert (atomic::is_always_lock_free, + // "timestamp is not lock-free on this architecture"); + +#if !defined(ATOMIC_LLONG_LOCK_FREE) || ATOMIC_LLONG_LOCK_FREE != 2 +# error timestamp is not lock-free on this architecture +#endif + + // Note that the value is not used to synchronize any other state so we + // use the release-consume ordering (i.e., we are only interested in the + // mtime value being synchronized). + // + // Store it as an underlying representation (normally int64_t) since + // timestamp is not usable with atomic (non-noexcept default ctor). + // + mutable atomic mtime_ {timestamp_unknown_rep}; + }; + + // Filesystem path-based target. + // + class path_target: public mtime_target + { + public: + using mtime_target::mtime_target; + + typedef build2::path path_type; + + // Target path is an "atomic consistent cash". That is, it can be set at + // any time but any subsequent updates must set the same path. Or, in + // other words, once the path is set, it never changes. + // + // A set empty path may signify special unknown/undetermined location (for + // example an installed import library -- we know it's there, just not + // exactly where). In this case you would also normally set its mtime. We + // used to return a pointer to properly distinguish between not set and + // empty but that proved too tedious. Note that this means there could be + // a race between path and mtime (unless you lock the target in some other + // way; see file_rule) so for this case it makes sense to set the + // timestamp first. + // + const path_type& + path () const; + + const path_type& + path (path_type) const; + + timestamp + load_mtime () const {return mtime_target::load_mtime (path ());} + + // Derive a path from target's dir, name, and, if set, ext. If ext is not + // set, try to derive it using the target type extension function and + // fallback to default_ext, if specified. In both cases also update the + // target's extension (this becomes important if later we need to reliably + // determine whether this file has an extension; think hxx{foo.bar.} and + // hxx{*}:extension is empty). + // + // If name_prefix is not NULL, add it before the name part and after the + // directory. Similarly, if name_suffix is not NULL, add it after the name + // part and before the extension. + // + // Finally, if the path was already assigned to this target, then this + // function verifies that the two are the same. + // + const path_type& + derive_path (const char* default_ext = nullptr, + const char* name_prefix = nullptr, + const char* name_suffix = nullptr); + + // This version can be used to derive the path from another target's path + // by adding another extension. + // + const path_type& + derive_path (path_type base, const char* default_ext = nullptr); + + // As above but only derives (and returns) the extension (empty means no + // extension used). If search is true then look for the extension as if + // it was a prerequisite, not a target. + // + const string& + derive_extension (const char* default_ext = nullptr, bool search = false); + + // Const versions of the above that can be used on unlocked targets. Note + // that here we don't allow providing any defaults since you probably + // should only use this version if everything comes from the target itself + // (and is therefore atomic). + // + const path_type& + derive_path () const + { + return const_cast (this)->derive_path (); // MT-aware. + } + + const string& + derive_extension () const + { + return const_cast (this)->derive_extension (); // MT-aware. + } + + public: + static const target_type static_type; + + private: + // Note that the state is also used to synchronize the path value so + // we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + mutable atomic path_state_ {0}; + mutable path_type path_; + }; + + // File target. + // + class file: public path_target + { + public: + using path_target::path_target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Alias target. It represents a list of targets (its prerequisites) + // as a single "name". + // + class alias: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Directory target. Note that this is not a filesystem directory + // but rather an alias target with the directory name. For actual + // filesystem directory (creation), see fsdir. + // + class dir: public alias + { + public: + using alias::alias; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + + public: + template + static const target* + search_implied (const scope&, const K&, tracer&); + }; + + // While a filesystem directory is mtime-based, the semantics is + // not very useful in our case. In particular, if another target + // depends on fsdir{}, then all that's desired is the creation of + // the directory if it doesn't already exist. In particular, we + // don't want to update the target just because some unrelated + // entry was created in that directory. + // + class fsdir: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Executable file. + // + class exe: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class buildfile: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // This is the venerable .in ("input") file that needs some kind of + // preprocessing. + // + // One interesting aspect of this target type is that the prerequisite + // search is target-dependent. Consider: + // + // hxx{version}: in{version.hxx} // version.hxx.in -> version.hxx + // + // Having to specify the header extension explicitly is inelegant. Instead + // what we really want to write is this: + // + // hxx{version}: in{version} + // + // But how do we know that in{version} means version.hxx.in? That's where + // the target-dependent search comes in: we take into account the target + // we are a prerequisite of. + // + class in: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Common documentation file targets. + // + // @@ Maybe these should be in the built-in doc module? + // + class doc: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // The problem with man pages is this: different platforms have + // different sets of sections. What seems to be the "sane" set + // is 1-9 (Linux and BSDs). SysV (e.g., Solaris) instead maps + // 8 to 1M (system administration). The section determines two + // things: the directory where the page is installed (e.g., + // /usr/share/man/man1) as well as the extension of the file + // (e.g., test.1). Note also that there could be sub-sections, + // e.g., 1p (for POSIX). Such a page would still go into man1 + // but will have the .1p extension (at least that's what happens + // on Linux). The challenge is to somehow handle this in a + // portable manner. So here is the plan: + // + // First of all, we have the man{} target type which can be used + // for a custom man page. That is, you can have any extension and + // install it anywhere you please: + // + // man{foo.X}: install = man/manX + // + // Then we have man1..9{} target types which model the "sane" + // section set and that would be automatically installed into + // correct locations on other platforms. In other words, the + // idea is that you should be able to have the foo.8 file, + // write man8{foo} and have it installed as man1m/foo.1m on + // some SysV host. + // + // Re-mapping the installation directory is easy: to help with + // that we have assigned install.man1..9 directory names. The + // messy part is to change the extension. It seems the only + // way to do that would be to have special logic for man pages + // in the generic install rule. @@ This is still a TODO. + // + // Note that handling subsections with man1..9{} is easy, we + // simply specify the extension explicitly, e.g., man{foo.1p}. + // + class man: public doc + { + public: + using doc::doc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class man1: public man + { + public: + using man::man; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + // Common implementation of the target factory, extension, and + // search functions. + // + template + pair> + target_factory (const target_type&, + dir_path d, + dir_path o, + string n, + optional e) + { + return make_pair (new T (move (d), move (o), move (n)), move (e)); + } + + // Return fixed target extension. + // + template + optional + target_extension_fix (const target_key&, const scope&, bool); + + template + bool + target_pattern_fix (const target_type&, const scope&, string&, bool); + + // Get the extension from the variable or use the default if none set. If + // the default is NULL, then return NULL. + // + template + optional + target_extension_var (const target_key&, const scope&, bool); + + template + bool + target_pattern_var (const target_type&, const scope&, string&, bool); + + // Always return NULL extension. + // + optional + target_extension_null (const target_key&, const scope&, bool); + + // Assert if called. + // + optional + target_extension_assert (const target_key&, const scope&, bool); + + // Target print functions. + // + + // Target type uses the extension but it is fixed and there is no use + // printing it (e.g., man1{}). + // + void + target_print_0_ext_verb (ostream&, const target_key&); + + // Target type uses the extension and there is normally no default so it + // should be printed (e.g., file{}). + // + void + target_print_1_ext_verb (ostream&, const target_key&); + + // The default behavior, that is, look for an existing target in the + // prerequisite's directory scope. + // + const target* + target_search (const target&, const prerequisite_key&); + + // First look for an existing target as above. If not found, then look + // for an existing file in the target-type-specific list of paths. + // + const target* + file_search (const target&, const prerequisite_key&); +} + +#include +#include + +#endif // BUILD2_TARGET_HXX diff --git a/build2/target.ixx b/build2/target.ixx index eb7dbaa..6a754d6 100644 --- a/build2/target.ixx +++ b/build2/target.ixx @@ -197,7 +197,7 @@ namespace build2 // prerequisite_members // group_view - resolve_group_members (action, const target&); // + resolve_group_members (action, const target&); // template inline auto prerequisite_members_range::iterator:: diff --git a/build2/target.txx b/build2/target.txx index 5abdfc5..d2afd59 100644 --- a/build2/target.txx +++ b/build2/target.txx @@ -2,11 +2,11 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include // dir_iterator +#include // dir_iterator -#include -#include -#include +#include +#include +#include namespace build2 { diff --git a/build2/test/common b/build2/test/common deleted file mode 100644 index 1a3117a..0000000 --- a/build2/test/common +++ /dev/null @@ -1,52 +0,0 @@ -// file : build2/test/common -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_COMMON -#define BUILD2_TEST_COMMON - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - enum class output_before {fail, warn, clean}; - enum class output_after {clean, keep}; - - struct common - { - // The config.test.output values. - // - output_before before = output_before::warn; - output_after after = output_after::clean; - - // The config.test query interface. - // - const names* test_ = nullptr; // The config.test value if any. - scope* root_ = nullptr; // The root scope for target resolution. - - // Return true if the specified alias target should pass-through to its - // prerequisites. - // - bool - pass (const target& alias_target) const; - - // Return true if the specified target should be tested. - // - bool - test (const target& test_target) const; - - // Return true if the specified target should be tested with the - // specified testscript test (or group). - // - bool - test (const target& test_target, const path& id_path) const; - }; - } -} - -#endif // BUILD2_TEST_COMMON diff --git a/build2/test/common.cxx b/build2/test/common.cxx index be0e690..8197d5d 100644 --- a/build2/test/common.cxx +++ b/build2/test/common.cxx @@ -2,10 +2,10 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include +#include +#include using namespace std; diff --git a/build2/test/common.hxx b/build2/test/common.hxx new file mode 100644 index 0000000..89b7581 --- /dev/null +++ b/build2/test/common.hxx @@ -0,0 +1,52 @@ +// file : build2/test/common.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_COMMON_HXX +#define BUILD2_TEST_COMMON_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + enum class output_before {fail, warn, clean}; + enum class output_after {clean, keep}; + + struct common + { + // The config.test.output values. + // + output_before before = output_before::warn; + output_after after = output_after::clean; + + // The config.test query interface. + // + const names* test_ = nullptr; // The config.test value if any. + scope* root_ = nullptr; // The root scope for target resolution. + + // Return true if the specified alias target should pass-through to its + // prerequisites. + // + bool + pass (const target& alias_target) const; + + // Return true if the specified target should be tested. + // + bool + test (const target& test_target) const; + + // Return true if the specified target should be tested with the + // specified testscript test (or group). + // + bool + test (const target& test_target, const path& id_path) const; + }; + } +} + +#endif // BUILD2_TEST_COMMON_HXX diff --git a/build2/test/init b/build2/test/init deleted file mode 100644 index 45aa547..0000000 --- a/build2/test/init +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/test/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_INIT -#define BUILD2_TEST_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - void - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_TEST_INIT diff --git a/build2/test/init.cxx b/build2/test/init.cxx index a367206..93ca1e4 100644 --- a/build2/test/init.cxx +++ b/build2/test/init.cxx @@ -2,18 +2,18 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/test/init.hxx b/build2/test/init.hxx new file mode 100644 index 0000000..2ba6ec6 --- /dev/null +++ b/build2/test/init.hxx @@ -0,0 +1,31 @@ +// file : build2/test/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_INIT_HXX +#define BUILD2_TEST_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + void + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_TEST_INIT_HXX diff --git a/build2/test/module b/build2/test/module deleted file mode 100644 index 49b3031..0000000 --- a/build2/test/module +++ /dev/null @@ -1,26 +0,0 @@ -// file : build2/test/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_MODULE -#define BUILD2_TEST_MODULE - -#include -#include - -#include - -#include -#include - -namespace build2 -{ - namespace test - { - struct module: module_base, virtual common, rule, alias_rule - { - }; - } -} - -#endif // BUILD2_TEST_MODULE diff --git a/build2/test/module.hxx b/build2/test/module.hxx new file mode 100644 index 0000000..3c9539f --- /dev/null +++ b/build2/test/module.hxx @@ -0,0 +1,26 @@ +// file : build2/test/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_MODULE_HXX +#define BUILD2_TEST_MODULE_HXX + +#include +#include + +#include + +#include +#include + +namespace build2 +{ + namespace test + { + struct module: module_base, virtual common, rule, alias_rule + { + }; + } +} + +#endif // BUILD2_TEST_MODULE_HXX diff --git a/build2/test/operation b/build2/test/operation deleted file mode 100644 index 5d2a229..0000000 --- a/build2/test/operation +++ /dev/null @@ -1,21 +0,0 @@ -// file : build2/test/operation -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_OPERATION -#define BUILD2_TEST_OPERATION - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - extern const operation_info test; - } -} - -#endif // BUILD2_TEST_OPERATION diff --git a/build2/test/operation.cxx b/build2/test/operation.cxx index 1f59fc1..46b9366 100644 --- a/build2/test/operation.cxx +++ b/build2/test/operation.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; using namespace butl; diff --git a/build2/test/operation.hxx b/build2/test/operation.hxx new file mode 100644 index 0000000..d834edf --- /dev/null +++ b/build2/test/operation.hxx @@ -0,0 +1,21 @@ +// file : build2/test/operation.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_OPERATION_HXX +#define BUILD2_TEST_OPERATION_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + extern const operation_info test; + } +} + +#endif // BUILD2_TEST_OPERATION_HXX diff --git a/build2/test/rule b/build2/test/rule deleted file mode 100644 index 7de2e24..0000000 --- a/build2/test/rule +++ /dev/null @@ -1,52 +0,0 @@ -// file : build2/test/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_RULE -#define BUILD2_TEST_RULE - -#include -#include - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - class rule_common: public build2::rule, protected virtual common - { - public: - virtual match_result - match (action, target&, const string&) const override; - - target_state - perform_script (action, const target&) const; - }; - - class rule: public rule_common - { - public: - virtual recipe - apply (action, target&) const override; - - static target_state - perform_test (action, const target&); - }; - - class alias_rule: public rule_common - { - public: - virtual recipe - apply (action, target&) const override; - - target_state - perform_test (action, const target&) const; - }; - } -} - -#endif // BUILD2_TEST_RULE diff --git a/build2/test/rule.cxx b/build2/test/rule.cxx index 64292fc..db4083e 100644 --- a/build2/test/rule.cxx +++ b/build2/test/rule.cxx @@ -2,19 +2,19 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/test/rule.hxx b/build2/test/rule.hxx new file mode 100644 index 0000000..819121c --- /dev/null +++ b/build2/test/rule.hxx @@ -0,0 +1,52 @@ +// file : build2/test/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_RULE_HXX +#define BUILD2_TEST_RULE_HXX + +#include +#include + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + class rule_common: public build2::rule, protected virtual common + { + public: + virtual match_result + match (action, target&, const string&) const override; + + target_state + perform_script (action, const target&) const; + }; + + class rule: public rule_common + { + public: + virtual recipe + apply (action, target&) const override; + + static target_state + perform_test (action, const target&); + }; + + class alias_rule: public rule_common + { + public: + virtual recipe + apply (action, target&) const override; + + target_state + perform_test (action, const target&) const; + }; + } +} + +#endif // BUILD2_TEST_RULE_HXX diff --git a/build2/test/script/builtin b/build2/test/script/builtin deleted file mode 100644 index 7d902ea..0000000 --- a/build2/test/script/builtin +++ /dev/null @@ -1,75 +0,0 @@ -// file : build2/test/script/builtin -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_BUILTIN -#define BUILD2_TEST_SCRIPT_BUILTIN - -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - class scope; - - // A process/thread-like object representing a running builtin. - // - // For now, instead of allocating the result storage dynamically, we - // expect it to be provided by the caller. - // - class builtin - { - public: - uint8_t - wait () {if (t_.joinable ()) t_.join (); return r_;} - - ~builtin () {wait ();} - - public: - builtin (uint8_t& r, thread&& t = thread ()): r_ (r), t_ (move (t)) {} - - builtin (builtin&&) = default; - builtin& operator= (builtin&&) = default; - - private: - uint8_t& r_; - thread t_; - }; - - // Start builtin command. Throw system_error on failure. - // - // Note that unlike argc/argv, our args don't include the program name. - // - using builtin_func = builtin (scope&, - uint8_t& result, - const strings& args, - auto_fd in, auto_fd out, auto_fd err); - - class builtin_map: public std::map - { - public: - using base = std::map; - using base::base; - - // Return NULL if not a builtin. - // - builtin_func* - find (const string& n) const - { - auto i (base::find (n)); - return i != end () ? i->second : nullptr; - } - }; - - extern const builtin_map builtins; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_BUILTIN diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx index a2f3107..d45b200 100644 --- a/build2/test/script/builtin.cxx +++ b/build2/test/script/builtin.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #ifndef _WIN32 # include @@ -14,13 +14,13 @@ #include #include -#include // use default operator<< implementation -#include // fdopen_mode, fdstream_mode -#include // mkdir_status +#include // use default operator<< implementation +#include // fdopen_mode, fdstream_mode +#include // mkdir_status -#include +#include -#include +#include // Strictly speaking a builtin which reads/writes from/to standard streams // must be asynchronous so that the caller can communicate with it through diff --git a/build2/test/script/builtin.hxx b/build2/test/script/builtin.hxx new file mode 100644 index 0000000..ae0681f --- /dev/null +++ b/build2/test/script/builtin.hxx @@ -0,0 +1,75 @@ +// file : build2/test/script/builtin.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_BUILTIN_HXX +#define BUILD2_TEST_SCRIPT_BUILTIN_HXX + +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + class scope; + + // A process/thread-like object representing a running builtin. + // + // For now, instead of allocating the result storage dynamically, we + // expect it to be provided by the caller. + // + class builtin + { + public: + uint8_t + wait () {if (t_.joinable ()) t_.join (); return r_;} + + ~builtin () {wait ();} + + public: + builtin (uint8_t& r, thread&& t = thread ()): r_ (r), t_ (move (t)) {} + + builtin (builtin&&) = default; + builtin& operator= (builtin&&) = default; + + private: + uint8_t& r_; + thread t_; + }; + + // Start builtin command. Throw system_error on failure. + // + // Note that unlike argc/argv, our args don't include the program name. + // + using builtin_func = builtin (scope&, + uint8_t& result, + const strings& args, + auto_fd in, auto_fd out, auto_fd err); + + class builtin_map: public std::map + { + public: + using base = std::map; + using base::base; + + // Return NULL if not a builtin. + // + builtin_func* + find (const string& n) const + { + auto i (base::find (n)); + return i != end () ? i->second : nullptr; + } + }; + + extern const builtin_map builtins; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_BUILTIN_HXX diff --git a/build2/test/script/lexer b/build2/test/script/lexer deleted file mode 100644 index 4851e13..0000000 --- a/build2/test/script/lexer +++ /dev/null @@ -1,90 +0,0 @@ -// file : build2/test/script/lexer -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_LEXER -#define BUILD2_TEST_SCRIPT_LEXER - -#include -#include - -#include - -#include - -namespace build2 -{ - namespace test - { - namespace script - { - struct lexer_mode: build2::lexer_mode - { - using base_type = build2::lexer_mode; - - enum - { - command_line = base_type::value_next, - first_token, // Expires at the end of the token. - second_token, // Expires at the end of the token. - variable_line, // Expires at the end of the line. - command_expansion, - here_line_single, - here_line_double, - description_line // Expires at the end of the line. - }; - - lexer_mode () = default; - lexer_mode (value_type v): base_type (v) {} - lexer_mode (base_type v): base_type (v) {} - }; - - class lexer: public build2::lexer - { - public: - using base_lexer = build2::lexer; - using base_mode = build2::lexer_mode; - - lexer (istream& is, - const path& name, - lexer_mode m, - const char* escapes = nullptr) - : base_lexer (is, name, nullptr, false) - { - mode (m, '\0', escapes); - } - - virtual void - mode (base_mode, - char = '\0', - optional = nullopt) override; - - // Number of quoted (double or single) tokens since last reset. - // - size_t - quoted () const {return quoted_;} - - void - reset_quoted (size_t q) {quoted_ = q;} - - virtual token - next () override; - - protected: - token - next_line (); - - token - next_description (); - - virtual token - word (state, bool) override; - - protected: - size_t quoted_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_LEXER diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx index fab5cc2..4d139ce 100644 --- a/build2/test/script/lexer.cxx +++ b/build2/test/script/lexer.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // strchr() diff --git a/build2/test/script/lexer.hxx b/build2/test/script/lexer.hxx new file mode 100644 index 0000000..a262764 --- /dev/null +++ b/build2/test/script/lexer.hxx @@ -0,0 +1,90 @@ +// file : build2/test/script/lexer.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_LEXER_HXX +#define BUILD2_TEST_SCRIPT_LEXER_HXX + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace test + { + namespace script + { + struct lexer_mode: build2::lexer_mode + { + using base_type = build2::lexer_mode; + + enum + { + command_line = base_type::value_next, + first_token, // Expires at the end of the token. + second_token, // Expires at the end of the token. + variable_line, // Expires at the end of the line. + command_expansion, + here_line_single, + here_line_double, + description_line // Expires at the end of the line. + }; + + lexer_mode () = default; + lexer_mode (value_type v): base_type (v) {} + lexer_mode (base_type v): base_type (v) {} + }; + + class lexer: public build2::lexer + { + public: + using base_lexer = build2::lexer; + using base_mode = build2::lexer_mode; + + lexer (istream& is, + const path& name, + lexer_mode m, + const char* escapes = nullptr) + : base_lexer (is, name, nullptr, false) + { + mode (m, '\0', escapes); + } + + virtual void + mode (base_mode, + char = '\0', + optional = nullopt) override; + + // Number of quoted (double or single) tokens since last reset. + // + size_t + quoted () const {return quoted_;} + + void + reset_quoted (size_t q) {quoted_ = q;} + + virtual token + next () override; + + protected: + token + next_line (); + + token + next_description (); + + virtual token + word (state, bool) override; + + protected: + size_t quoted_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_LEXER_HXX diff --git a/build2/test/script/parser b/build2/test/script/parser deleted file mode 100644 index e018548..0000000 --- a/build2/test/script/parser +++ /dev/null @@ -1,245 +0,0 @@ -// file : build2/test/script/parser -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_PARSER -#define BUILD2_TEST_SCRIPT_PARSER - -#include -#include - -#include -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - class lexer; - class runner; - - class parser: protected build2::parser - { - // Pre-parse. Issue diagnostics and throw failed in case of an error. - // - public: - void - pre_parse (script&); - - void - pre_parse (istream&, script&); - - // Helpers. - // - // Parse attribute string and perform attribute-guided assignment. - // Issue diagnostics and throw failed in case of an error. - // - void - apply_value_attributes (const variable*, // Optional. - value& lhs, - value&& rhs, - const string& attributes, - token_type assign_kind, - const path& name); // For diagnostics. - - // Recursive descent parser. - // - // Usually (but not always) parse functions receive the token/type - // from which it should start consuming and in return the token/type - // should contain the first token that has not been consumed. - // - // Functions that are called parse_*() rather than pre_parse_*() are - // used for both stages. - // - protected: - bool - pre_parse_demote_group_scope (unique_ptr&); - - token - pre_parse_scope_body (); - - unique_ptr - pre_parse_scope_block (token&, token_type&, const string&); - - bool - pre_parse_line (token&, token_type&, - optional&, - lines* = nullptr, - bool one = false); - - bool - pre_parse_if_else (token&, token_type&, - optional&, - lines&); - - bool - pre_parse_if_else_scope (token&, token_type&, - optional&, - lines&); - - bool - pre_parse_if_else_command (token&, token_type&, - optional&, - lines&); - - void - pre_parse_directive (token&, token_type&); - - void - pre_parse_include_line (names, location); - - description - pre_parse_leading_description (token&, token_type&); - - description - parse_trailing_description (token&, token_type&); - - value - parse_variable_line (token&, token_type&); - - command_expr - parse_command_line (token&, token_type&); - - // Ordered sequence of here-document redirects that we can expect to - // see after the command line. - // - struct here_redirect - { - size_t expr; // Index in command_expr. - size_t pipe; // Index in command_pipe. - int fd; // Redirect fd (0 - in, 1 - out, 2 - err). - }; - - struct here_doc - { - // Redirects that share here_doc. Most of the time we will have no - // more than 2 (2 - for the roundtrip test cases). - // - small_vector redirects; - - string end; - bool literal; // Literal (single-quote). - string modifiers; - - // Regex introducer ('\0' if not a regex, so can be used as bool). - // - char regex; - - // Regex global flags. Meaningful if regex != '\0'. - // - string regex_flags; - }; - using here_docs = vector; - - pair - parse_command_expr (token&, token_type&); - - command_exit - parse_command_exit (token&, token_type&); - - void - parse_here_documents (token&, token_type&, - pair&); - - struct parsed_doc - { - union - { - string str; // Here-document literal. - regex_lines regex; // Here-document regex. - }; - - bool re; // True if regex. - uint64_t end_line; // Here-document end marker location. - uint64_t end_column; - - parsed_doc (string, uint64_t line, uint64_t column); - parsed_doc (regex_lines&&, uint64_t line, uint64_t column); - parsed_doc (parsed_doc&&); // Note: move constuctible-only type. - ~parsed_doc (); - }; - - parsed_doc - parse_here_document (token&, token_type&, - const string&, - const string& mode, - char re_intro); // '\0' if not a regex. - - // Execute. Issue diagnostics and throw failed in case of an error. - // - public: - void - execute (script& s, runner& r); - - void - execute (scope&, script&, runner&); - - protected: - void - exec_scope_body (); - - void - exec_lines (lines::iterator, lines::iterator, size_t&, command_type); - - // Customization hooks. - // - protected: - virtual lookup - lookup_variable (name&&, string&&, const location&) override; - - // Number of quoted tokens since last reset. Note that this includes - // the peeked token, if any. - // - protected: - size_t - quoted () const; - - void - reset_quoted (token& current); - - size_t replay_quoted_; - - // Insert id into the id map checking for duplicates. - // - protected: - const string& - insert_id (string, location); - - // Set lexer pointers for both the current and the base classes. - // - protected: - void - set_lexer (lexer* l); - - protected: - using base_parser = build2::parser; - - script* script_; - - // Pre-parse state. - // - using id_map = std::unordered_map; - using include_set = std::set; - - group* group_; - id_map* id_map_; - include_set* include_set_; // Testscripts already included in this - // scope. Must be absolute and normalized. - lexer* lexer_; - string id_prefix_; // Auto-derived id prefix. - - // Execute state. - // - runner* runner_; - scope* scope_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_PARSER diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index 350ca44..ae6bdbc 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -2,14 +2,14 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include -#include // sched, keep_going +#include // sched, keep_going -#include -#include +#include +#include using namespace std; diff --git a/build2/test/script/parser.hxx b/build2/test/script/parser.hxx new file mode 100644 index 0000000..21ea61a --- /dev/null +++ b/build2/test/script/parser.hxx @@ -0,0 +1,245 @@ +// file : build2/test/script/parser.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_PARSER_HXX +#define BUILD2_TEST_SCRIPT_PARSER_HXX + +#include +#include + +#include +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + class lexer; + class runner; + + class parser: protected build2::parser + { + // Pre-parse. Issue diagnostics and throw failed in case of an error. + // + public: + void + pre_parse (script&); + + void + pre_parse (istream&, script&); + + // Helpers. + // + // Parse attribute string and perform attribute-guided assignment. + // Issue diagnostics and throw failed in case of an error. + // + void + apply_value_attributes (const variable*, // Optional. + value& lhs, + value&& rhs, + const string& attributes, + token_type assign_kind, + const path& name); // For diagnostics. + + // Recursive descent parser. + // + // Usually (but not always) parse functions receive the token/type + // from which it should start consuming and in return the token/type + // should contain the first token that has not been consumed. + // + // Functions that are called parse_*() rather than pre_parse_*() are + // used for both stages. + // + protected: + bool + pre_parse_demote_group_scope (unique_ptr&); + + token + pre_parse_scope_body (); + + unique_ptr + pre_parse_scope_block (token&, token_type&, const string&); + + bool + pre_parse_line (token&, token_type&, + optional&, + lines* = nullptr, + bool one = false); + + bool + pre_parse_if_else (token&, token_type&, + optional&, + lines&); + + bool + pre_parse_if_else_scope (token&, token_type&, + optional&, + lines&); + + bool + pre_parse_if_else_command (token&, token_type&, + optional&, + lines&); + + void + pre_parse_directive (token&, token_type&); + + void + pre_parse_include_line (names, location); + + description + pre_parse_leading_description (token&, token_type&); + + description + parse_trailing_description (token&, token_type&); + + value + parse_variable_line (token&, token_type&); + + command_expr + parse_command_line (token&, token_type&); + + // Ordered sequence of here-document redirects that we can expect to + // see after the command line. + // + struct here_redirect + { + size_t expr; // Index in command_expr. + size_t pipe; // Index in command_pipe. + int fd; // Redirect fd (0 - in, 1 - out, 2 - err). + }; + + struct here_doc + { + // Redirects that share here_doc. Most of the time we will have no + // more than 2 (2 - for the roundtrip test cases). + // + small_vector redirects; + + string end; + bool literal; // Literal (single-quote). + string modifiers; + + // Regex introducer ('\0' if not a regex, so can be used as bool). + // + char regex; + + // Regex global flags. Meaningful if regex != '\0'. + // + string regex_flags; + }; + using here_docs = vector; + + pair + parse_command_expr (token&, token_type&); + + command_exit + parse_command_exit (token&, token_type&); + + void + parse_here_documents (token&, token_type&, + pair&); + + struct parsed_doc + { + union + { + string str; // Here-document literal. + regex_lines regex; // Here-document regex. + }; + + bool re; // True if regex. + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + parsed_doc (string, uint64_t line, uint64_t column); + parsed_doc (regex_lines&&, uint64_t line, uint64_t column); + parsed_doc (parsed_doc&&); // Note: move constuctible-only type. + ~parsed_doc (); + }; + + parsed_doc + parse_here_document (token&, token_type&, + const string&, + const string& mode, + char re_intro); // '\0' if not a regex. + + // Execute. Issue diagnostics and throw failed in case of an error. + // + public: + void + execute (script& s, runner& r); + + void + execute (scope&, script&, runner&); + + protected: + void + exec_scope_body (); + + void + exec_lines (lines::iterator, lines::iterator, size_t&, command_type); + + // Customization hooks. + // + protected: + virtual lookup + lookup_variable (name&&, string&&, const location&) override; + + // Number of quoted tokens since last reset. Note that this includes + // the peeked token, if any. + // + protected: + size_t + quoted () const; + + void + reset_quoted (token& current); + + size_t replay_quoted_; + + // Insert id into the id map checking for duplicates. + // + protected: + const string& + insert_id (string, location); + + // Set lexer pointers for both the current and the base classes. + // + protected: + void + set_lexer (lexer* l); + + protected: + using base_parser = build2::parser; + + script* script_; + + // Pre-parse state. + // + using id_map = std::unordered_map; + using include_set = std::set; + + group* group_; + id_map* id_map_; + include_set* include_set_; // Testscripts already included in this + // scope. Must be absolute and normalized. + lexer* lexer_; + string id_prefix_; // Auto-derived id prefix. + + // Execute state. + // + runner* runner_; + scope* scope_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_PARSER_HXX diff --git a/build2/test/script/regex b/build2/test/script/regex deleted file mode 100644 index 1170b99..0000000 --- a/build2/test/script/regex +++ /dev/null @@ -1,684 +0,0 @@ -// file : build2/test/script/regex -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_REGEX -#define BUILD2_TEST_SCRIPT_REGEX - -#include -#include -#include -#include // basic_string -#include // uintptr_t -#include // make_unsigned, enable_if, is_* -#include - -#include -#include - -namespace build2 -{ - namespace test - { - namespace script - { - namespace regex - { - using char_string = std::basic_string; - - enum class char_flags: uint16_t - { - icase = 0x1, // Case-insensitive match. - idot = 0x2, // Invert '.' escaping. - - none = 0 - }; - - // Restricts valid standard flags to just {icase}, extends with custom - // flags {idot}. - // - class char_regex: public std::basic_regex - { - public: - using base_type = std::basic_regex; - - char_regex (const char_string&, char_flags = char_flags::none); - }; - - // Newlines are line separators and are not part of the line: - // - // lineline - // - // Specifically, this means that a customary trailing newline creates a - // trailing blank line. - // - // All characters can inter-compare (though there cannot be regex - // characters in the output, only in line_regex). - // - // Note that we assume that line_regex and the input to regex_match() - // use the same pool. - // - struct line_pool - { - // Note that we assume the pool can be moved without invalidating - // pointers to any already pooled entities. - // - std::unordered_set strings; - std::list regexes; - }; - - enum class line_type - { - special, - literal, - regex - }; - - struct line_char - { - // Steal last two bits from the pointer to store the type. - // - private: - std::uintptr_t data_; - - public: - line_type - type () const {return static_cast (data_ & 0x3);} - - int - special () const - { - // Stored as (shifted) int16_t. Perform steps reversed to those - // that are described in the comment for the corresponding ctor. - // Note that the intermediate cast to uint16_t is required to - // portably preserve the -1 special character. - // - return static_cast (static_cast (data_ >> 2)); - } - - const char_string* - literal () const - { - // Note that 2 rightmost bits are used for packaging line_char - // type. Read the comment for the corresponding ctor for details. - // - return reinterpret_cast ( - data_ & ~std::uintptr_t (0x3)); - } - - const char_regex* - regex () const - { - // Note that 2 rightmost bits are used for packaging line_char - // type. Read the comment for the corresponding ctor for details. - // - return reinterpret_cast ( - data_ & ~std::uintptr_t (0x3)); - } - - static const line_char nul; - static const line_char eof; - - // Note: creates an uninitialized value. - // - line_char () = default; - - // Create a special character. The argument value must be one of the - // following ones: - // - // 0 (nul character) - // -1 (EOF) - // [()|.*+?{}\0123456789,=!] (excluding []) - // - // Note that the constructor is implicit to allow basic_regex to - // implicitly construct line_chars from special char literals (in - // particular libstdc++ appends them to an internal line_string). - // - // Also note that we extend the valid characters set (see above) with - // 'p', 'n' (used by libstdc++ for positive/negative look-ahead - // tokens representation), and '\n', '\r', u'\u2028', u'\u2029' (used - // by libstdc++ for newline/newparagraph matching). - // - line_char (int); - - // Create a literal character. - // - // Don't copy string if already pooled. - // - explicit - line_char (const char_string&, line_pool&); - - explicit - line_char (char_string&&, line_pool&); - - explicit - line_char (const char_string* s) // Assume already pooled. - // - // Steal two bits from the pointer to package line_char type. - // Assume (and statically assert) that char_string address is a - // multiple of four. - // - : data_ (reinterpret_cast (s) | - static_cast (line_type::literal)) {} - - // Create a regex character. - // - explicit - line_char (char_regex, line_pool&); - - explicit - line_char (const char_regex* r) // Assume already pooled. - // - // Steal two bits from the pointer to package line_char type. - // Assume (and statically assert) that char_regex address is a - // multiple of four. - // - : data_ (reinterpret_cast (r) | - static_cast (line_type::regex)) {} - - // Provide basic_regex with the ability to use line_char in a context - // where a char value is expected (e.g., as a function argument). - // - // libstdc++ seems to cast special line_chars only (and such a - // conversion is meanigfull). - // - // msvcrt casts line_chars of arbitrary types instead. The only - // reasonable strategy is to return a value that differs from any - // other that can be encountered in a regex expression and so will - // unlikelly be misinterpreted. - // - operator char () const - { - return type () == line_type::special ? special () : '\a'; // BELL. - } - - // Return true if the character is a syntax (special) one. - // - static bool - syntax (char); - - // Provide basic_regex (such as from msvcrt) with the ability to - // explicitly cast line_chars to implementation-specific enums. - // - template - explicit - operator T () const - { - assert (type () == line_type::special); - return static_cast (special ()); - } - }; - - // Perform "deep" characters comparison (for example match literal - // character with a regex character), rather than just compare them - // literally. At least one argument must be of a type other than regex - // as there is no operator==() defined to compare regexes. Characters - // of the literal type must share the same pool (strings are compared - // by pointers not by values). - // - bool - operator== (const line_char&, const line_char&); - - // Return false if arguments are equal (operator==() returns true). - // Otherwise if types are different return the value implying that - // special < literal < regex. If types are special or literal return - // the result of the respective characters or strings comparison. At - // least one argument must be of a type other than regex as there is no - // operator<() defined to compare regexes. - // - // While not very natural operation for the class we have, we have to - // provide some meaningfull semantics for such a comparison as it is - // required by the char_traits specialization. While we - // could provide it right in that specialization, let's keep it here - // for basic_regex implementations that potentially can compare - // line_chars as they compare them with expressions of other types (see - // below). - // - bool - operator< (const line_char&, const line_char&); - - inline bool - operator!= (const line_char& l, const line_char& r) - { - return !(l == r); - } - - inline bool - operator<= (const line_char& l, const line_char& r) - { - return l < r || l == r; - } - - // Provide basic_regex (such as from msvcrt) with the ability to - // compare line_char to a value of an integral or - // implementation-specific enum type. In the absense of the following - // template operators, such a comparisons would be ambigious for - // integral types (given that there are implicit conversions - // int->line_char and line_char->char) and impossible for enums. - // - // Note that these == and < operators can succeed only for a line_char - // of the special type. For other types they always return false. That - // in particular leads to the following case: - // - // (lc != c) != (lc < c || c < lc). - // - // Note that we can not assert line_char is of the special type as - // basic_regex (such as from libc++) may need the ability to check if - // arbitrary line_char belongs to some special characters range (like - // ['0', '9']). - // - template - struct line_char_cmp - : public std::enable_if::value || - (std::is_enum::value && - !std::is_same::value)> {}; - - template ::type> - bool - operator== (const line_char& l, const T& r) - { - return l.type () == line_type::special && - static_cast (l.special ()) == r; - } - - template ::type> - bool - operator== (const T& l, const line_char& r) - { - return r.type () == line_type::special && - static_cast (r.special ()) == l; - } - - template ::type> - bool - operator!= (const line_char& l, const T& r) - { - return !(l == r); - } - - template ::type> - bool - operator!= (const T& l, const line_char& r) - { - return !(l == r); - } - - template ::type> - bool - operator< (const line_char& l, const T& r) - { - return l.type () == line_type::special && - static_cast (l.special ()) < r; - } - - template ::type> - bool - operator< (const T& l, const line_char& r) - { - return r.type () == line_type::special && - l < static_cast (r.special ()); - } - - template ::type> - inline bool - operator<= (const line_char& l, const T& r) - { - return l < r || l == r; - } - - template ::type> - inline bool - operator<= (const T& l, const line_char& r) - { - return l < r || l == r; - } - - using line_string = std::basic_string; - - // Locale that has ctype facet installed. Used in the - // regex_traits specialization (see below). - // - class line_char_locale: public std::locale - { - public: - // Create a copy of the global C++ locale. - // - line_char_locale (); - }; - } - } - } -} - -// Standard template specializations for line_char that are required for the -// basic_regex instantiation. -// -namespace std -{ - template <> - class char_traits - { - public: - using char_type = build2::test::script::regex::line_char; - using int_type = char_type; - using off_type = char_traits::off_type; - using pos_type = char_traits::pos_type; - using state_type = char_traits::state_type; - - static void - assign (char_type& c1, const char_type& c2) {c1 = c2;} - - static char_type* - assign (char_type*, size_t, char_type); - - // Note that eq() and lt() are not constexpr (as required by C++11) - // because == and < operators for char_type are not constexpr. - // - static bool - eq (const char_type& l, const char_type& r) {return l == r;} - - static bool - lt (const char_type& l, const char_type& r) {return l < r;} - - static char_type* - move (char_type*, const char_type*, size_t); - - static char_type* - copy (char_type*, const char_type*, size_t); - - static int - compare (const char_type*, const char_type*, size_t); - - static size_t - length (const char_type*); - - static const char_type* - find (const char_type*, size_t, const char_type&); - - static constexpr char_type - to_char_type (const int_type& c) {return c;} - - static constexpr int_type - to_int_type (const char_type& c) {return int_type (c);} - - // Note that the following functions are not constexpr (as required by - // C++11) because their return expressions are not constexpr. - // - static bool - eq_int_type (const int_type& l, const int_type& r) {return l == r;} - - static int_type eof () {return char_type::eof;} - - static int_type - not_eof (const int_type& c) - { - return c != char_type::eof ? c : char_type::nul; - } - }; - - // ctype<> must be derived from both ctype_base and locale::facet (the later - // supports ref-counting used by the std::locale implementation internally). - // - // msvcrt for some reason also derives ctype_base from locale::facet which - // produces "already a base-class" warning and effectivelly breaks the - // reference counting. So we derive from ctype_base only in this case. - // - template <> - class ctype: public ctype_base -#if !defined(_MSC_VER) || _MSC_VER > 1910 - , public locale::facet -#endif - { - // Used by the implementation only. - // - using line_type = build2::test::script::regex::line_type; - - public: - using char_type = build2::test::script::regex::line_char; - - static locale::id id; - -#if !defined(_MSC_VER) || _MSC_VER > 1910 - explicit - ctype (size_t refs = 0): locale::facet (refs) {} -#else - explicit - ctype (size_t refs = 0): ctype_base (refs) {} -#endif - - // While unnecessary, let's keep for completeness. - // - virtual - ~ctype () override = default; - - // The C++ standard requires the following functions to call their virtual - // (protected) do_*() counterparts that provide the real implementations. - // The only purpose for this indirection is to provide a user with the - // ability to customize existing (standard) ctype facets. As we do not - // provide such an ability, for simplicity we will omit the do_*() - // functions and provide the implementations directly. This should be safe - // as nobody except us could call those protected functions. - // - bool - is (mask m, char_type c) const - { - return m == - (c.type () == line_type::special && build2::digit (c.special ()) - ? digit - : 0); - } - - const char_type* - is (const char_type*, const char_type*, mask*) const; - - const char_type* - scan_is (mask, const char_type*, const char_type*) const; - - const char_type* - scan_not (mask, const char_type*, const char_type*) const; - - char_type - toupper (char_type c) const {return c;} - - const char_type* - toupper (char_type*, const char_type* e) const {return e;} - - char_type - tolower (char_type c) const {return c;} - - const char_type* - tolower (char_type*, const char_type* e) const {return e;} - - char_type - widen (char c) const {return char_type (c);} - - const char* - widen (const char*, const char*, char_type*) const; - - char - narrow (char_type c, char def) const - { - return c.type () == line_type::special ? c.special () : def; - } - - const char_type* - narrow (const char_type*, const char_type*, char, char*) const; - }; - - // Note: the current application locale must be the POSIX one. Otherwise the - // behavior is undefined. - // - template <> - class regex_traits - { - public: - using char_type = build2::test::script::regex::line_char; - using string_type = build2::test::script::regex::line_string; - using locale_type = build2::test::script::regex::line_char_locale; - using char_class_type = regex_traits::char_class_type; - - // Workaround for msvcrt bugs. For some reason it assumes such a members - // to be present in a regex_traits specialization. - // -#if defined(_MSC_VER) && _MSC_VER <= 1910 - static const ctype_base::mask _Ch_upper = ctype_base::upper; - static const ctype_base::mask _Ch_alpha = ctype_base::alpha; - - // Unsigned char_type. msvcrt statically asserts the _Uelem type is - // unsigned, so we specialize is_unsigned as well (see below). - // - using _Uelem = char_type; -#endif - - regex_traits () = default; // Unnecessary but let's keep for completeness. - - static size_t - length (const char_type* p) {return string_type::traits_type::length (p);} - - char_type - translate (char_type c) const {return c;} - - // Case-insensitive matching is not supported by line_regex. So there is no - // reason for the function to be called. - // - char_type - translate_nocase (char_type c) const {assert (false); return c;} - - // Return a sort-key - the exact copy of [b, e). - // - template - string_type - transform (I b, I e) const {return string_type (b, e);} - - // Return a case-insensitive sort-key. Case-insensitive matching is not - // supported by line_regex. So there is no reason for the function to be - // called. - // - template - string_type - transform_primary (I b, I e) const - { - assert (false); - return string_type (b, e); - } - - // POSIX regex grammar and collating elements (e.g., [.tilde.]) in - // particular are not supported. So there is no reason for the function to - // be called. - // - template - string_type - lookup_collatename (I, I) const {assert (false); return string_type ();} - - // Character classes (e.g., [:lower:]) are not supported. So there is no - // reason for the function to be called. - // - template - char_class_type - lookup_classname (I, I, bool = false) const - { - assert (false); - return char_class_type (); - } - - // Return false as we don't support character classes (e.g., [:lower:]). - // - bool - isctype (char_type, char_class_type) const {return false;} - - int - value (char_type, int) const; - - // Return the locale passed as an argument as we do not expect anything - // other than POSIX locale, that we also assume to be imbued by default. - // - locale_type - imbue (locale_type l) {return l;} - - locale_type - getloc () const {return locale_type ();} - }; - - // We assume line_char to be an unsigned type and express that with the - // following specializations used by basic_regex implementations. - // - // libstdc++ defines unsigned CharT type (regex_traits template parameter) - // to use as an index in some internal cache regardless if the cache is used - // for this specialization (and the cache is used only if CharT is char). - // - template <> - struct make_unsigned - { - using type = build2::test::script::regex::line_char; - }; - - // msvcrt assumes regex_traits::_Uelem to be present (see above) - // and statically asserts it is unsigned. - // - template <> - struct is_unsigned - { - static const bool value = true; - }; - - // When used with libc++ the linker complains that it can't find - // __match_any_but_newline::__exec() function. The problem is - // that the function is only specialized for char and wchar_t - // (LLVM bug #31409). As line_char has no notion of the newline character we - // specialize the class template to behave as the __match_any - // instantiation does (that luckily has all the functions in place). - // -#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 4000 - template <> - class __match_any_but_newline - : public __match_any - { - public: - using base = __match_any; - using base::base; - }; -#endif -} - -namespace build2 -{ - namespace test - { - namespace script - { - namespace regex - { - class line_regex: public std::basic_regex - { - public: - using base_type = std::basic_regex; - - using base_type::base_type; - - line_regex () = default; - - // Move string regex together with the pool used to create it. - // - line_regex (line_string&& s, line_pool&& p) - // No move-string ctor for base_type, so emulate it. - // - : base_type (s), pool (move (p)) {s.clear ();} - - // Move constuctible/assignable-only type. - // - line_regex (line_regex&&) = default; - line_regex (const line_regex&) = delete; - line_regex& operator= (line_regex&&) = default; - line_regex& operator= (const line_regex&) = delete; - - public: - line_pool pool; - }; - } - } - } -} - -#include - -#endif // BUILD2_TEST_SCRIPT_REGEX diff --git a/build2/test/script/regex.cxx b/build2/test/script/regex.cxx index b77f8a5..bf38a62 100644 --- a/build2/test/script/regex.cxx +++ b/build2/test/script/regex.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // copy(), copy_backward() diff --git a/build2/test/script/regex.hxx b/build2/test/script/regex.hxx new file mode 100644 index 0000000..d4f1cf5 --- /dev/null +++ b/build2/test/script/regex.hxx @@ -0,0 +1,684 @@ +// file : build2/test/script/regex.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_REGEX_HXX +#define BUILD2_TEST_SCRIPT_REGEX_HXX + +#include +#include +#include +#include // basic_string +#include // uintptr_t +#include // make_unsigned, enable_if, is_* +#include + +#include +#include + +namespace build2 +{ + namespace test + { + namespace script + { + namespace regex + { + using char_string = std::basic_string; + + enum class char_flags: uint16_t + { + icase = 0x1, // Case-insensitive match. + idot = 0x2, // Invert '.' escaping. + + none = 0 + }; + + // Restricts valid standard flags to just {icase}, extends with custom + // flags {idot}. + // + class char_regex: public std::basic_regex + { + public: + using base_type = std::basic_regex; + + char_regex (const char_string&, char_flags = char_flags::none); + }; + + // Newlines are line separators and are not part of the line: + // + // lineline + // + // Specifically, this means that a customary trailing newline creates a + // trailing blank line. + // + // All characters can inter-compare (though there cannot be regex + // characters in the output, only in line_regex). + // + // Note that we assume that line_regex and the input to regex_match() + // use the same pool. + // + struct line_pool + { + // Note that we assume the pool can be moved without invalidating + // pointers to any already pooled entities. + // + std::unordered_set strings; + std::list regexes; + }; + + enum class line_type + { + special, + literal, + regex + }; + + struct line_char + { + // Steal last two bits from the pointer to store the type. + // + private: + std::uintptr_t data_; + + public: + line_type + type () const {return static_cast (data_ & 0x3);} + + int + special () const + { + // Stored as (shifted) int16_t. Perform steps reversed to those + // that are described in the comment for the corresponding ctor. + // Note that the intermediate cast to uint16_t is required to + // portably preserve the -1 special character. + // + return static_cast (static_cast (data_ >> 2)); + } + + const char_string* + literal () const + { + // Note that 2 rightmost bits are used for packaging line_char + // type. Read the comment for the corresponding ctor for details. + // + return reinterpret_cast ( + data_ & ~std::uintptr_t (0x3)); + } + + const char_regex* + regex () const + { + // Note that 2 rightmost bits are used for packaging line_char + // type. Read the comment for the corresponding ctor for details. + // + return reinterpret_cast ( + data_ & ~std::uintptr_t (0x3)); + } + + static const line_char nul; + static const line_char eof; + + // Note: creates an uninitialized value. + // + line_char () = default; + + // Create a special character. The argument value must be one of the + // following ones: + // + // 0 (nul character) + // -1 (EOF) + // [()|.*+?{}\0123456789,=!] (excluding []) + // + // Note that the constructor is implicit to allow basic_regex to + // implicitly construct line_chars from special char literals (in + // particular libstdc++ appends them to an internal line_string). + // + // Also note that we extend the valid characters set (see above) with + // 'p', 'n' (used by libstdc++ for positive/negative look-ahead + // tokens representation), and '\n', '\r', u'\u2028', u'\u2029' (used + // by libstdc++ for newline/newparagraph matching). + // + line_char (int); + + // Create a literal character. + // + // Don't copy string if already pooled. + // + explicit + line_char (const char_string&, line_pool&); + + explicit + line_char (char_string&&, line_pool&); + + explicit + line_char (const char_string* s) // Assume already pooled. + // + // Steal two bits from the pointer to package line_char type. + // Assume (and statically assert) that char_string address is a + // multiple of four. + // + : data_ (reinterpret_cast (s) | + static_cast (line_type::literal)) {} + + // Create a regex character. + // + explicit + line_char (char_regex, line_pool&); + + explicit + line_char (const char_regex* r) // Assume already pooled. + // + // Steal two bits from the pointer to package line_char type. + // Assume (and statically assert) that char_regex address is a + // multiple of four. + // + : data_ (reinterpret_cast (r) | + static_cast (line_type::regex)) {} + + // Provide basic_regex with the ability to use line_char in a context + // where a char value is expected (e.g., as a function argument). + // + // libstdc++ seems to cast special line_chars only (and such a + // conversion is meanigfull). + // + // msvcrt casts line_chars of arbitrary types instead. The only + // reasonable strategy is to return a value that differs from any + // other that can be encountered in a regex expression and so will + // unlikelly be misinterpreted. + // + operator char () const + { + return type () == line_type::special ? special () : '\a'; // BELL. + } + + // Return true if the character is a syntax (special) one. + // + static bool + syntax (char); + + // Provide basic_regex (such as from msvcrt) with the ability to + // explicitly cast line_chars to implementation-specific enums. + // + template + explicit + operator T () const + { + assert (type () == line_type::special); + return static_cast (special ()); + } + }; + + // Perform "deep" characters comparison (for example match literal + // character with a regex character), rather than just compare them + // literally. At least one argument must be of a type other than regex + // as there is no operator==() defined to compare regexes. Characters + // of the literal type must share the same pool (strings are compared + // by pointers not by values). + // + bool + operator== (const line_char&, const line_char&); + + // Return false if arguments are equal (operator==() returns true). + // Otherwise if types are different return the value implying that + // special < literal < regex. If types are special or literal return + // the result of the respective characters or strings comparison. At + // least one argument must be of a type other than regex as there is no + // operator<() defined to compare regexes. + // + // While not very natural operation for the class we have, we have to + // provide some meaningfull semantics for such a comparison as it is + // required by the char_traits specialization. While we + // could provide it right in that specialization, let's keep it here + // for basic_regex implementations that potentially can compare + // line_chars as they compare them with expressions of other types (see + // below). + // + bool + operator< (const line_char&, const line_char&); + + inline bool + operator!= (const line_char& l, const line_char& r) + { + return !(l == r); + } + + inline bool + operator<= (const line_char& l, const line_char& r) + { + return l < r || l == r; + } + + // Provide basic_regex (such as from msvcrt) with the ability to + // compare line_char to a value of an integral or + // implementation-specific enum type. In the absense of the following + // template operators, such a comparisons would be ambigious for + // integral types (given that there are implicit conversions + // int->line_char and line_char->char) and impossible for enums. + // + // Note that these == and < operators can succeed only for a line_char + // of the special type. For other types they always return false. That + // in particular leads to the following case: + // + // (lc != c) != (lc < c || c < lc). + // + // Note that we can not assert line_char is of the special type as + // basic_regex (such as from libc++) may need the ability to check if + // arbitrary line_char belongs to some special characters range (like + // ['0', '9']). + // + template + struct line_char_cmp + : public std::enable_if::value || + (std::is_enum::value && + !std::is_same::value)> {}; + + template ::type> + bool + operator== (const line_char& l, const T& r) + { + return l.type () == line_type::special && + static_cast (l.special ()) == r; + } + + template ::type> + bool + operator== (const T& l, const line_char& r) + { + return r.type () == line_type::special && + static_cast (r.special ()) == l; + } + + template ::type> + bool + operator!= (const line_char& l, const T& r) + { + return !(l == r); + } + + template ::type> + bool + operator!= (const T& l, const line_char& r) + { + return !(l == r); + } + + template ::type> + bool + operator< (const line_char& l, const T& r) + { + return l.type () == line_type::special && + static_cast (l.special ()) < r; + } + + template ::type> + bool + operator< (const T& l, const line_char& r) + { + return r.type () == line_type::special && + l < static_cast (r.special ()); + } + + template ::type> + inline bool + operator<= (const line_char& l, const T& r) + { + return l < r || l == r; + } + + template ::type> + inline bool + operator<= (const T& l, const line_char& r) + { + return l < r || l == r; + } + + using line_string = std::basic_string; + + // Locale that has ctype facet installed. Used in the + // regex_traits specialization (see below). + // + class line_char_locale: public std::locale + { + public: + // Create a copy of the global C++ locale. + // + line_char_locale (); + }; + } + } + } +} + +// Standard template specializations for line_char that are required for the +// basic_regex instantiation. +// +namespace std +{ + template <> + class char_traits + { + public: + using char_type = build2::test::script::regex::line_char; + using int_type = char_type; + using off_type = char_traits::off_type; + using pos_type = char_traits::pos_type; + using state_type = char_traits::state_type; + + static void + assign (char_type& c1, const char_type& c2) {c1 = c2;} + + static char_type* + assign (char_type*, size_t, char_type); + + // Note that eq() and lt() are not constexpr (as required by C++11) + // because == and < operators for char_type are not constexpr. + // + static bool + eq (const char_type& l, const char_type& r) {return l == r;} + + static bool + lt (const char_type& l, const char_type& r) {return l < r;} + + static char_type* + move (char_type*, const char_type*, size_t); + + static char_type* + copy (char_type*, const char_type*, size_t); + + static int + compare (const char_type*, const char_type*, size_t); + + static size_t + length (const char_type*); + + static const char_type* + find (const char_type*, size_t, const char_type&); + + static constexpr char_type + to_char_type (const int_type& c) {return c;} + + static constexpr int_type + to_int_type (const char_type& c) {return int_type (c);} + + // Note that the following functions are not constexpr (as required by + // C++11) because their return expressions are not constexpr. + // + static bool + eq_int_type (const int_type& l, const int_type& r) {return l == r;} + + static int_type eof () {return char_type::eof;} + + static int_type + not_eof (const int_type& c) + { + return c != char_type::eof ? c : char_type::nul; + } + }; + + // ctype<> must be derived from both ctype_base and locale::facet (the later + // supports ref-counting used by the std::locale implementation internally). + // + // msvcrt for some reason also derives ctype_base from locale::facet which + // produces "already a base-class" warning and effectivelly breaks the + // reference counting. So we derive from ctype_base only in this case. + // + template <> + class ctype: public ctype_base +#if !defined(_MSC_VER) || _MSC_VER > 1910 + , public locale::facet +#endif + { + // Used by the implementation only. + // + using line_type = build2::test::script::regex::line_type; + + public: + using char_type = build2::test::script::regex::line_char; + + static locale::id id; + +#if !defined(_MSC_VER) || _MSC_VER > 1910 + explicit + ctype (size_t refs = 0): locale::facet (refs) {} +#else + explicit + ctype (size_t refs = 0): ctype_base (refs) {} +#endif + + // While unnecessary, let's keep for completeness. + // + virtual + ~ctype () override = default; + + // The C++ standard requires the following functions to call their virtual + // (protected) do_*() counterparts that provide the real implementations. + // The only purpose for this indirection is to provide a user with the + // ability to customize existing (standard) ctype facets. As we do not + // provide such an ability, for simplicity we will omit the do_*() + // functions and provide the implementations directly. This should be safe + // as nobody except us could call those protected functions. + // + bool + is (mask m, char_type c) const + { + return m == + (c.type () == line_type::special && build2::digit (c.special ()) + ? digit + : 0); + } + + const char_type* + is (const char_type*, const char_type*, mask*) const; + + const char_type* + scan_is (mask, const char_type*, const char_type*) const; + + const char_type* + scan_not (mask, const char_type*, const char_type*) const; + + char_type + toupper (char_type c) const {return c;} + + const char_type* + toupper (char_type*, const char_type* e) const {return e;} + + char_type + tolower (char_type c) const {return c;} + + const char_type* + tolower (char_type*, const char_type* e) const {return e;} + + char_type + widen (char c) const {return char_type (c);} + + const char* + widen (const char*, const char*, char_type*) const; + + char + narrow (char_type c, char def) const + { + return c.type () == line_type::special ? c.special () : def; + } + + const char_type* + narrow (const char_type*, const char_type*, char, char*) const; + }; + + // Note: the current application locale must be the POSIX one. Otherwise the + // behavior is undefined. + // + template <> + class regex_traits + { + public: + using char_type = build2::test::script::regex::line_char; + using string_type = build2::test::script::regex::line_string; + using locale_type = build2::test::script::regex::line_char_locale; + using char_class_type = regex_traits::char_class_type; + + // Workaround for msvcrt bugs. For some reason it assumes such a members + // to be present in a regex_traits specialization. + // +#if defined(_MSC_VER) && _MSC_VER <= 1910 + static const ctype_base::mask _Ch_upper = ctype_base::upper; + static const ctype_base::mask _Ch_alpha = ctype_base::alpha; + + // Unsigned char_type. msvcrt statically asserts the _Uelem type is + // unsigned, so we specialize is_unsigned as well (see below). + // + using _Uelem = char_type; +#endif + + regex_traits () = default; // Unnecessary but let's keep for completeness. + + static size_t + length (const char_type* p) {return string_type::traits_type::length (p);} + + char_type + translate (char_type c) const {return c;} + + // Case-insensitive matching is not supported by line_regex. So there is no + // reason for the function to be called. + // + char_type + translate_nocase (char_type c) const {assert (false); return c;} + + // Return a sort-key - the exact copy of [b, e). + // + template + string_type + transform (I b, I e) const {return string_type (b, e);} + + // Return a case-insensitive sort-key. Case-insensitive matching is not + // supported by line_regex. So there is no reason for the function to be + // called. + // + template + string_type + transform_primary (I b, I e) const + { + assert (false); + return string_type (b, e); + } + + // POSIX regex grammar and collating elements (e.g., [.tilde.]) in + // particular are not supported. So there is no reason for the function to + // be called. + // + template + string_type + lookup_collatename (I, I) const {assert (false); return string_type ();} + + // Character classes (e.g., [:lower:]) are not supported. So there is no + // reason for the function to be called. + // + template + char_class_type + lookup_classname (I, I, bool = false) const + { + assert (false); + return char_class_type (); + } + + // Return false as we don't support character classes (e.g., [:lower:]). + // + bool + isctype (char_type, char_class_type) const {return false;} + + int + value (char_type, int) const; + + // Return the locale passed as an argument as we do not expect anything + // other than POSIX locale, that we also assume to be imbued by default. + // + locale_type + imbue (locale_type l) {return l;} + + locale_type + getloc () const {return locale_type ();} + }; + + // We assume line_char to be an unsigned type and express that with the + // following specializations used by basic_regex implementations. + // + // libstdc++ defines unsigned CharT type (regex_traits template parameter) + // to use as an index in some internal cache regardless if the cache is used + // for this specialization (and the cache is used only if CharT is char). + // + template <> + struct make_unsigned + { + using type = build2::test::script::regex::line_char; + }; + + // msvcrt assumes regex_traits::_Uelem to be present (see above) + // and statically asserts it is unsigned. + // + template <> + struct is_unsigned + { + static const bool value = true; + }; + + // When used with libc++ the linker complains that it can't find + // __match_any_but_newline::__exec() function. The problem is + // that the function is only specialized for char and wchar_t + // (LLVM bug #31409). As line_char has no notion of the newline character we + // specialize the class template to behave as the __match_any + // instantiation does (that luckily has all the functions in place). + // +#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 4000 + template <> + class __match_any_but_newline + : public __match_any + { + public: + using base = __match_any; + using base::base; + }; +#endif +} + +namespace build2 +{ + namespace test + { + namespace script + { + namespace regex + { + class line_regex: public std::basic_regex + { + public: + using base_type = std::basic_regex; + + using base_type::base_type; + + line_regex () = default; + + // Move string regex together with the pool used to create it. + // + line_regex (line_string&& s, line_pool&& p) + // No move-string ctor for base_type, so emulate it. + // + : base_type (s), pool (move (p)) {s.clear ();} + + // Move constuctible/assignable-only type. + // + line_regex (line_regex&&) = default; + line_regex (const line_regex&) = delete; + line_regex& operator= (line_regex&&) = default; + line_regex& operator= (const line_regex&) = delete; + + public: + line_pool pool; + }; + } + } + } +} + +#include + +#endif // BUILD2_TEST_SCRIPT_REGEX_HXX diff --git a/build2/test/script/runner b/build2/test/script/runner deleted file mode 100644 index 566b9de..0000000 --- a/build2/test/script/runner +++ /dev/null @@ -1,90 +0,0 @@ -// file : build2/test/script/runner -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_RUNNER -#define BUILD2_TEST_SCRIPT_RUNNER - -#include -#include - -#include // location - -#include - -namespace build2 -{ - namespace test - { - struct common; - - namespace script - { - class runner - { - public: - // Return false if this test/group should be skipped. - // - virtual bool - test (scope&) const = 0; - - // Location is the scope start location (for diagnostics, etc). - // - virtual void - enter (scope&, const location&) = 0; - - // Index is the 1-base index of this command line in the command list - // (e.g., in a compound test). If it is 0 then it means there is only - // one command (e.g., a simple test). This information can be used, - // for example, to derive file names. - // - // Location is the start position of this command line in the - // testscript. It can be used in diagnostics. - // - virtual void - run (scope&, - const command_expr&, command_type, - size_t index, - const location&) = 0; - - virtual bool - run_if (scope&, const command_expr&, size_t, const location&) = 0; - - // Location is the scope end location (for diagnostics, etc). - // - virtual void - leave (scope&, const location&) = 0; - }; - - class default_runner: public runner - { - public: - explicit - default_runner (const common& c): common_ (c) {} - - virtual bool - test (scope& s) const override; - - virtual void - enter (scope&, const location&) override; - - virtual void - run (scope&, - const command_expr&, command_type, - size_t, - const location&) override; - - virtual bool - run_if (scope&, const command_expr&, size_t, const location&) override; - - virtual void - leave (scope&, const location&) override; - - private: - const common& common_; - }; - } - } -} - -#endif // BUILD2_TEST_SCRIPT_RUNNER diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx index 105039a..ce32150 100644 --- a/build2/test/script/runner.cxx +++ b/build2/test/script/runner.cxx @@ -2,22 +2,22 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include #include // streamsize -#include // fdopen_mode, fdnull(), fddup() +#include // fdopen_mode, fdnull(), fddup() -#include -#include -#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/test/script/runner.hxx b/build2/test/script/runner.hxx new file mode 100644 index 0000000..7833692 --- /dev/null +++ b/build2/test/script/runner.hxx @@ -0,0 +1,90 @@ +// file : build2/test/script/runner.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_RUNNER_HXX +#define BUILD2_TEST_SCRIPT_RUNNER_HXX + +#include +#include + +#include // location + +#include + +namespace build2 +{ + namespace test + { + struct common; + + namespace script + { + class runner + { + public: + // Return false if this test/group should be skipped. + // + virtual bool + test (scope&) const = 0; + + // Location is the scope start location (for diagnostics, etc). + // + virtual void + enter (scope&, const location&) = 0; + + // Index is the 1-base index of this command line in the command list + // (e.g., in a compound test). If it is 0 then it means there is only + // one command (e.g., a simple test). This information can be used, + // for example, to derive file names. + // + // Location is the start position of this command line in the + // testscript. It can be used in diagnostics. + // + virtual void + run (scope&, + const command_expr&, command_type, + size_t index, + const location&) = 0; + + virtual bool + run_if (scope&, const command_expr&, size_t, const location&) = 0; + + // Location is the scope end location (for diagnostics, etc). + // + virtual void + leave (scope&, const location&) = 0; + }; + + class default_runner: public runner + { + public: + explicit + default_runner (const common& c): common_ (c) {} + + virtual bool + test (scope& s) const override; + + virtual void + enter (scope&, const location&) override; + + virtual void + run (scope&, + const command_expr&, command_type, + size_t, + const location&) override; + + virtual bool + run_if (scope&, const command_expr&, size_t, const location&) override; + + virtual void + leave (scope&, const location&) override; + + private: + const common& common_; + }; + } + } +} + +#endif // BUILD2_TEST_SCRIPT_RUNNER_HXX diff --git a/build2/test/script/script b/build2/test/script/script deleted file mode 100644 index 9a54c71..0000000 --- a/build2/test/script/script +++ /dev/null @@ -1,552 +0,0 @@ -// file : build2/test/script/script -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_SCRIPT -#define BUILD2_TEST_SCRIPT_SCRIPT - -#include - -#include -#include - -#include - -#include - -#include // replay_tokens - -namespace build2 -{ - class target; - - namespace test - { - namespace script - { - class parser; // Required by VC for 'friend class parser' declaration. - - // Pre-parse representation. - // - - enum class line_type - { - var, - cmd, - cmd_if, - cmd_ifn, - cmd_elif, - cmd_elifn, - cmd_else, - cmd_end - }; - - ostream& - operator<< (ostream&, line_type); - - struct line - { - line_type type; - replay_tokens tokens; - - union - { - const variable* var; // Pre-entered for line_type::var. - }; - }; - - // Most of the time we will have just one line (test command). - // - using lines = small_vector; - - // Parse object model. - // - - // redirect - // - enum class redirect_type - { - none, - pass, - null, - trace, - merge, - here_str_literal, - here_str_regex, - here_doc_literal, - here_doc_regex, - here_doc_ref, // Reference to here_doc literal or regex. - file, - }; - - // Pre-parsed (but not instantiated) regex lines. The idea here is that - // we should be able to re-create their (more or less) exact text - // representation for diagnostics but also instantiate without any - // re-parsing. - // - struct regex_line - { - // If regex is true, then value is the regex expression. Otherwise, it - // is a literal. Note that special characters can be present in both - // cases. For example, //+ is a regex, while /+ is a literal, both - // with '+' as a special character. Flags are only valid for regex. - // Literals falls apart into textual (has no special characters) and - // special (has just special characters instead) ones. For example - // foo is a textual literal, while /.+ is a special one. Note that - // literal must not have value and special both non-empty. - // - bool regex; - - string value; - string flags; - string special; - - uint64_t line; - uint64_t column; - - // Create regex with optional special characters. - // - regex_line (uint64_t l, uint64_t c, - string v, string f, string s = string ()) - : regex (true), - value (move (v)), - flags (move (f)), - special (move (s)), - line (l), - column (c) {} - - // Create a literal, either text or special. - // - regex_line (uint64_t l, uint64_t c, string v, bool s) - : regex (false), - value (s ? string () : move (v)), - special (s ? move (v) : string ()), - line (l), - column (c) {} - }; - - struct regex_lines - { - char intro; // Introducer character. - string flags; // Global flags (here-document). - - small_vector lines; - }; - - // Output file redirect mode. - // - enum class redirect_fmode - { - compare, - overwrite, - append - }; - - struct redirect - { - redirect_type type; - - struct file_type - { - using path_type = build2::path; - path_type path; - redirect_fmode mode; // Meaningless for input redirect. - }; - - union - { - int fd; // Merge-to descriptor. - string str; // Note: with trailing newline, if requested. - regex_lines regex; // Note: with trailing blank, if requested. - file_type file; - reference_wrapper ref; // Note: no chains. - }; - - string modifiers; // Redirect modifiers. - string end; // Here-document end marker (no regex intro/flags). - uint64_t end_line; // Here-document end marker location. - uint64_t end_column; - - // Create redirect of a type other than reference. - // - explicit - redirect (redirect_type = redirect_type::none); - - // Create redirect of the reference type. - // - redirect (redirect_type t, const redirect& r) - : type (redirect_type::here_doc_ref), ref (r) - { - // There is no support (and need) for reference chains. - // - assert (t == redirect_type::here_doc_ref && - r.type != redirect_type::here_doc_ref); - } - - // Move constuctible/assignable-only type. - // - redirect (redirect&&); - redirect& operator= (redirect&&); - - ~redirect (); - - const redirect& - effective () const noexcept - { - return type == redirect_type::here_doc_ref ? ref.get () : *this; - } - }; - - // cleanup - // - enum class cleanup_type - { - always, // &foo - cleanup, fail if does not exist. - maybe, // &?foo - cleanup, ignore if does not exist. - never // &!foo - don’t cleanup, ignore if doesn’t exist. - }; - - // File or directory to be automatically cleaned up at the end of the - // scope. If the path ends with a trailing slash, then it is assumed to - // be a directory, otherwise -- a file. A directory that is about to be - // cleaned up must be empty. - // - // The last component in the path may contain a wildcard that have the - // following semantics: - // - // dir/* - remove all immediate files - // dir/*/ - remove all immediate sub-directories (must be empty) - // dir/** - remove all files recursively - // dir/**/ - remove all sub-directories recursively (must be empty) - // dir/*** - remove directory dir with all files and sub-directories - // recursively - // - struct cleanup - { - cleanup_type type; - build2::path path; - }; - using cleanups = vector; - - // command_exit - // - enum class exit_comparison {eq, ne}; - - struct command_exit - { - // C/C++ don't apply constraints on program exit code other than it - // being of type int. - // - // POSIX specifies that only the least significant 8 bits shall be - // available from wait() and waitpid(); the full value shall be - // available from waitid() (read more at _Exit, _exit Open Group - // spec). - // - // While the Linux man page for waitid() doesn't mention any - // deviations from the standard, the FreeBSD implementation (as of - // version 11.0) only returns 8 bits like the other wait*() calls. - // - // Windows supports 32-bit exit codes. - // - // Note that in shells some exit values can have special meaning so - // using them can be a source of confusion. For bash values in the - // [126, 255] range are such a special ones (see Appendix E, "Exit - // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). - // - exit_comparison comparison; - uint8_t status; - }; - - // command - // - struct command - { - path program; - strings arguments; - - redirect in; - redirect out; - redirect err; - - script::cleanups cleanups; - - command_exit exit {exit_comparison::eq, 0}; - }; - - enum class command_to_stream: uint16_t - { - header = 0x01, - here_doc = 0x02, // Note: printed on a new line. - all = header | here_doc - }; - - void - to_stream (ostream&, const command&, command_to_stream); - - ostream& - operator<< (ostream&, const command&); - - // command_pipe - // - using command_pipe = vector; - - void - to_stream (ostream&, const command_pipe&, command_to_stream); - - ostream& - operator<< (ostream&, const command_pipe&); - - // command_expr - // - enum class expr_operator {log_or, log_and}; - - struct expr_term - { - expr_operator op; // OR-ed to an implied false for the first term. - command_pipe pipe; - }; - - using command_expr = vector; - - void - to_stream (ostream&, const command_expr&, command_to_stream); - - ostream& - operator<< (ostream&, const command_expr&); - - // command_type - // - enum class command_type {test, setup, teardown}; - - ostream& - operator<< (ostream&, command_type); - - // description - // - struct description - { - string id; - string summary; - string details; - - bool - empty () const - { - return id.empty () && summary.empty () && details.empty (); - } - }; - - // scope - // - class script; - - enum class scope_state {unknown, passed, failed}; - - class scope - { - public: - scope* const parent; // NULL for the root (script) scope. - script* const root; // Self for the root (script) scope. - - // The chain of if-else scope alternatives. See also if_cond_ below. - // - unique_ptr if_chain; - - // Note that if we pass the variable name as a string, then it will - // be looked up in the wrong pool. - // - variable_map vars; - - const path& id_path; // Id path ($@, relative in POSIX form). - const dir_path& wd_path; // Working dir ($~, absolute and normalized). - - optional desc; - - scope_state state = scope_state::unknown; - test::script::cleanups cleanups; - - // Variables. - // - public: - // Lookup the variable starting from this scope, continuing with outer - // scopes, then the target being tested, then the testscript target, - // and then outer buildfile scopes (including testscript-type/pattern - // specific). - // - lookup - find (const variable&) const; - - // As above but only look for buildfile variables. If target_only is - // false then also look in scopes of the test target (this should only - // be done if the variable's visibility is target). - // - lookup - find_in_buildfile (const string&, bool target_only = true) const; - - // Return a value suitable for assignment. If the variable does not - // exist in this scope's map, then a new one with the NULL value is - // added and returned. Otherwise the existing value is returned. - // - value& - assign (const variable& var) {return vars.assign (var);} - - // Return a value suitable for append/prepend. If the variable does - // not exist in this scope's map, then outer scopes are searched for - // the same variable. If found then a new variable with the found - // value is added to this scope and returned. Otherwise this function - // proceeds as assign() above. - // - value& - append (const variable&); - - // Reset special $*, $N variables based on the test.* values. - // - void - reset_special (); - - // Cleanup. - // - public: - // Register a cleanup. If the cleanup is explicit, then override the - // cleanup type if this path is already registered. Ignore implicit - // registration of a path outside script working directory. - // - void - clean (cleanup, bool implicit); - - public: - virtual - ~scope () = default; - - protected: - scope (const string& id, scope* parent); - - // Pre-parse data. - // - public: - virtual bool - empty () const = 0; - - protected: - friend class parser; - - location start_loc_; - location end_loc_; - - optional if_cond_; - }; - - // group - // - class group: public scope - { - public: - vector> scopes; - - public: - group (const string& id, group& p): scope (id, &p) {} - - protected: - group (const string& id): scope (id, nullptr) {} // For root. - - // Pre-parse data. - // - public: - virtual bool - empty () const override - { - return - setup_.empty () && - tdown_.empty () && - find_if (scopes.begin (), scopes.end (), - [] (const unique_ptr& s) - { - return !s->empty (); - }) == scopes.end (); - } - - private: - friend class parser; - - lines setup_; - lines tdown_; - }; - - // test - // - class test: public scope - { - public: - test (const string& id, group& p): scope (id, &p) {} - - // Pre-parse data. - // - public: - virtual bool - empty () const override - { - return tests_.empty (); - } - - private: - friend class parser; - - lines tests_; - }; - - // script - // - class script_base // Make sure certain things are initialized early. - { - protected: - script_base (); - - public: - variable_pool var_pool; - mutable shared_mutex var_pool_mutex; - - const variable& test_var; // test - const variable& options_var; // test.options - const variable& arguments_var; // test.arguments - const variable& redirects_var; // test.redirects - const variable& cleanups_var; // test.cleanups - - const variable& wd_var; // $~ - const variable& id_var; // $@ - const variable& cmd_var; // $* - const variable* cmdN_var[10]; // $N - }; - - class script: public script_base, public group - { - public: - script (const target& test_target, - const testscript& script_target, - const dir_path& root_wd); - - script (script&&) = delete; - script (const script&) = delete; - script& operator= (script&&) = delete; - script& operator= (const script&) = delete; - - public: - const target& test_target; // Target we are testing. - const testscript& script_target; // Target of the testscript file. - - // Pre-parse data. - // - private: - friend class parser; - - // Testscript file paths. Specifically, replay_token::file points to - // these paths. - // - std::set paths_; - }; - } - } -} - -#include - -#endif // BUILD2_TEST_SCRIPT_SCRIPT diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx index d4945fe..25f72af 100644 --- a/build2/test/script/script.cxx +++ b/build2/test/script/script.cxx @@ -2,13 +2,13 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include #include // find() -#include -#include +#include +#include using namespace std; diff --git a/build2/test/script/script.hxx b/build2/test/script/script.hxx new file mode 100644 index 0000000..4f6fdab --- /dev/null +++ b/build2/test/script/script.hxx @@ -0,0 +1,552 @@ +// file : build2/test/script/script.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_SCRIPT_HXX +#define BUILD2_TEST_SCRIPT_SCRIPT_HXX + +#include + +#include +#include + +#include + +#include + +#include // replay_tokens + +namespace build2 +{ + class target; + + namespace test + { + namespace script + { + class parser; // Required by VC for 'friend class parser' declaration. + + // Pre-parse representation. + // + + enum class line_type + { + var, + cmd, + cmd_if, + cmd_ifn, + cmd_elif, + cmd_elifn, + cmd_else, + cmd_end + }; + + ostream& + operator<< (ostream&, line_type); + + struct line + { + line_type type; + replay_tokens tokens; + + union + { + const variable* var; // Pre-entered for line_type::var. + }; + }; + + // Most of the time we will have just one line (test command). + // + using lines = small_vector; + + // Parse object model. + // + + // redirect + // + enum class redirect_type + { + none, + pass, + null, + trace, + merge, + here_str_literal, + here_str_regex, + here_doc_literal, + here_doc_regex, + here_doc_ref, // Reference to here_doc literal or regex. + file, + }; + + // Pre-parsed (but not instantiated) regex lines. The idea here is that + // we should be able to re-create their (more or less) exact text + // representation for diagnostics but also instantiate without any + // re-parsing. + // + struct regex_line + { + // If regex is true, then value is the regex expression. Otherwise, it + // is a literal. Note that special characters can be present in both + // cases. For example, //+ is a regex, while /+ is a literal, both + // with '+' as a special character. Flags are only valid for regex. + // Literals falls apart into textual (has no special characters) and + // special (has just special characters instead) ones. For example + // foo is a textual literal, while /.+ is a special one. Note that + // literal must not have value and special both non-empty. + // + bool regex; + + string value; + string flags; + string special; + + uint64_t line; + uint64_t column; + + // Create regex with optional special characters. + // + regex_line (uint64_t l, uint64_t c, + string v, string f, string s = string ()) + : regex (true), + value (move (v)), + flags (move (f)), + special (move (s)), + line (l), + column (c) {} + + // Create a literal, either text or special. + // + regex_line (uint64_t l, uint64_t c, string v, bool s) + : regex (false), + value (s ? string () : move (v)), + special (s ? move (v) : string ()), + line (l), + column (c) {} + }; + + struct regex_lines + { + char intro; // Introducer character. + string flags; // Global flags (here-document). + + small_vector lines; + }; + + // Output file redirect mode. + // + enum class redirect_fmode + { + compare, + overwrite, + append + }; + + struct redirect + { + redirect_type type; + + struct file_type + { + using path_type = build2::path; + path_type path; + redirect_fmode mode; // Meaningless for input redirect. + }; + + union + { + int fd; // Merge-to descriptor. + string str; // Note: with trailing newline, if requested. + regex_lines regex; // Note: with trailing blank, if requested. + file_type file; + reference_wrapper ref; // Note: no chains. + }; + + string modifiers; // Redirect modifiers. + string end; // Here-document end marker (no regex intro/flags). + uint64_t end_line; // Here-document end marker location. + uint64_t end_column; + + // Create redirect of a type other than reference. + // + explicit + redirect (redirect_type = redirect_type::none); + + // Create redirect of the reference type. + // + redirect (redirect_type t, const redirect& r) + : type (redirect_type::here_doc_ref), ref (r) + { + // There is no support (and need) for reference chains. + // + assert (t == redirect_type::here_doc_ref && + r.type != redirect_type::here_doc_ref); + } + + // Move constuctible/assignable-only type. + // + redirect (redirect&&); + redirect& operator= (redirect&&); + + ~redirect (); + + const redirect& + effective () const noexcept + { + return type == redirect_type::here_doc_ref ? ref.get () : *this; + } + }; + + // cleanup + // + enum class cleanup_type + { + always, // &foo - cleanup, fail if does not exist. + maybe, // &?foo - cleanup, ignore if does not exist. + never // &!foo - don’t cleanup, ignore if doesn’t exist. + }; + + // File or directory to be automatically cleaned up at the end of the + // scope. If the path ends with a trailing slash, then it is assumed to + // be a directory, otherwise -- a file. A directory that is about to be + // cleaned up must be empty. + // + // The last component in the path may contain a wildcard that have the + // following semantics: + // + // dir/* - remove all immediate files + // dir/*/ - remove all immediate sub-directories (must be empty) + // dir/** - remove all files recursively + // dir/**/ - remove all sub-directories recursively (must be empty) + // dir/*** - remove directory dir with all files and sub-directories + // recursively + // + struct cleanup + { + cleanup_type type; + build2::path path; + }; + using cleanups = vector; + + // command_exit + // + enum class exit_comparison {eq, ne}; + + struct command_exit + { + // C/C++ don't apply constraints on program exit code other than it + // being of type int. + // + // POSIX specifies that only the least significant 8 bits shall be + // available from wait() and waitpid(); the full value shall be + // available from waitid() (read more at _Exit, _exit Open Group + // spec). + // + // While the Linux man page for waitid() doesn't mention any + // deviations from the standard, the FreeBSD implementation (as of + // version 11.0) only returns 8 bits like the other wait*() calls. + // + // Windows supports 32-bit exit codes. + // + // Note that in shells some exit values can have special meaning so + // using them can be a source of confusion. For bash values in the + // [126, 255] range are such a special ones (see Appendix E, "Exit + // Codes With Special Meanings" in the Advanced Bash-Scripting Guide). + // + exit_comparison comparison; + uint8_t status; + }; + + // command + // + struct command + { + path program; + strings arguments; + + redirect in; + redirect out; + redirect err; + + script::cleanups cleanups; + + command_exit exit {exit_comparison::eq, 0}; + }; + + enum class command_to_stream: uint16_t + { + header = 0x01, + here_doc = 0x02, // Note: printed on a new line. + all = header | here_doc + }; + + void + to_stream (ostream&, const command&, command_to_stream); + + ostream& + operator<< (ostream&, const command&); + + // command_pipe + // + using command_pipe = vector; + + void + to_stream (ostream&, const command_pipe&, command_to_stream); + + ostream& + operator<< (ostream&, const command_pipe&); + + // command_expr + // + enum class expr_operator {log_or, log_and}; + + struct expr_term + { + expr_operator op; // OR-ed to an implied false for the first term. + command_pipe pipe; + }; + + using command_expr = vector; + + void + to_stream (ostream&, const command_expr&, command_to_stream); + + ostream& + operator<< (ostream&, const command_expr&); + + // command_type + // + enum class command_type {test, setup, teardown}; + + ostream& + operator<< (ostream&, command_type); + + // description + // + struct description + { + string id; + string summary; + string details; + + bool + empty () const + { + return id.empty () && summary.empty () && details.empty (); + } + }; + + // scope + // + class script; + + enum class scope_state {unknown, passed, failed}; + + class scope + { + public: + scope* const parent; // NULL for the root (script) scope. + script* const root; // Self for the root (script) scope. + + // The chain of if-else scope alternatives. See also if_cond_ below. + // + unique_ptr if_chain; + + // Note that if we pass the variable name as a string, then it will + // be looked up in the wrong pool. + // + variable_map vars; + + const path& id_path; // Id path ($@, relative in POSIX form). + const dir_path& wd_path; // Working dir ($~, absolute and normalized). + + optional desc; + + scope_state state = scope_state::unknown; + test::script::cleanups cleanups; + + // Variables. + // + public: + // Lookup the variable starting from this scope, continuing with outer + // scopes, then the target being tested, then the testscript target, + // and then outer buildfile scopes (including testscript-type/pattern + // specific). + // + lookup + find (const variable&) const; + + // As above but only look for buildfile variables. If target_only is + // false then also look in scopes of the test target (this should only + // be done if the variable's visibility is target). + // + lookup + find_in_buildfile (const string&, bool target_only = true) const; + + // Return a value suitable for assignment. If the variable does not + // exist in this scope's map, then a new one with the NULL value is + // added and returned. Otherwise the existing value is returned. + // + value& + assign (const variable& var) {return vars.assign (var);} + + // Return a value suitable for append/prepend. If the variable does + // not exist in this scope's map, then outer scopes are searched for + // the same variable. If found then a new variable with the found + // value is added to this scope and returned. Otherwise this function + // proceeds as assign() above. + // + value& + append (const variable&); + + // Reset special $*, $N variables based on the test.* values. + // + void + reset_special (); + + // Cleanup. + // + public: + // Register a cleanup. If the cleanup is explicit, then override the + // cleanup type if this path is already registered. Ignore implicit + // registration of a path outside script working directory. + // + void + clean (cleanup, bool implicit); + + public: + virtual + ~scope () = default; + + protected: + scope (const string& id, scope* parent); + + // Pre-parse data. + // + public: + virtual bool + empty () const = 0; + + protected: + friend class parser; + + location start_loc_; + location end_loc_; + + optional if_cond_; + }; + + // group + // + class group: public scope + { + public: + vector> scopes; + + public: + group (const string& id, group& p): scope (id, &p) {} + + protected: + group (const string& id): scope (id, nullptr) {} // For root. + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return + setup_.empty () && + tdown_.empty () && + find_if (scopes.begin (), scopes.end (), + [] (const unique_ptr& s) + { + return !s->empty (); + }) == scopes.end (); + } + + private: + friend class parser; + + lines setup_; + lines tdown_; + }; + + // test + // + class test: public scope + { + public: + test (const string& id, group& p): scope (id, &p) {} + + // Pre-parse data. + // + public: + virtual bool + empty () const override + { + return tests_.empty (); + } + + private: + friend class parser; + + lines tests_; + }; + + // script + // + class script_base // Make sure certain things are initialized early. + { + protected: + script_base (); + + public: + variable_pool var_pool; + mutable shared_mutex var_pool_mutex; + + const variable& test_var; // test + const variable& options_var; // test.options + const variable& arguments_var; // test.arguments + const variable& redirects_var; // test.redirects + const variable& cleanups_var; // test.cleanups + + const variable& wd_var; // $~ + const variable& id_var; // $@ + const variable& cmd_var; // $* + const variable* cmdN_var[10]; // $N + }; + + class script: public script_base, public group + { + public: + script (const target& test_target, + const testscript& script_target, + const dir_path& root_wd); + + script (script&&) = delete; + script (const script&) = delete; + script& operator= (script&&) = delete; + script& operator= (const script&) = delete; + + public: + const target& test_target; // Target we are testing. + const testscript& script_target; // Target of the testscript file. + + // Pre-parse data. + // + private: + friend class parser; + + // Testscript file paths. Specifically, replay_token::file points to + // these paths. + // + std::set paths_; + }; + } + } +} + +#include + +#endif // BUILD2_TEST_SCRIPT_SCRIPT_HXX diff --git a/build2/test/script/token b/build2/test/script/token deleted file mode 100644 index 9d3e330..0000000 --- a/build2/test/script/token +++ /dev/null @@ -1,65 +0,0 @@ -// file : build2/test/script/token -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_SCRIPT_TOKEN -#define BUILD2_TEST_SCRIPT_TOKEN - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - namespace script - { - struct token_type: build2::token_type - { - using base_type = build2::token_type; - - enum - { - // NOTE: remember to update token_printer()! - - semi = base_type::value_next, // ; - - dot, // . - - plus, // + - minus, // - - - pipe, // | - clean, // &{?!} (modifiers in value) - - in_pass, // <| - in_null, // <- - in_str, // <{:} (modifiers in value) - in_doc, // <<{:} (modifiers in value) - in_file, // <<< - - out_pass, // >| - out_null, // >- - out_trace, // >! - out_merge, // >& - out_str, // >{:~} (modifiers in value) - out_doc, // >>{:~} (modifiers in value) - out_file_cmp, // >>> - out_file_ovr, // >= - out_file_app // >+ - }; - - token_type () = default; - token_type (value_type v): base_type (v) {} - token_type (base_type v): base_type (v) {} - }; - - void - token_printer (ostream&, const token&, bool); - } - } -} - -#endif // BUILD2_TEST_SCRIPT_TOKEN diff --git a/build2/test/script/token.cxx b/build2/test/script/token.cxx index 3d3dc8e..42428c6 100644 --- a/build2/test/script/token.cxx +++ b/build2/test/script/token.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/test/script/token.hxx b/build2/test/script/token.hxx new file mode 100644 index 0000000..0e8a577 --- /dev/null +++ b/build2/test/script/token.hxx @@ -0,0 +1,65 @@ +// file : build2/test/script/token.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_SCRIPT_TOKEN_HXX +#define BUILD2_TEST_SCRIPT_TOKEN_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + namespace script + { + struct token_type: build2::token_type + { + using base_type = build2::token_type; + + enum + { + // NOTE: remember to update token_printer()! + + semi = base_type::value_next, // ; + + dot, // . + + plus, // + + minus, // - + + pipe, // | + clean, // &{?!} (modifiers in value) + + in_pass, // <| + in_null, // <- + in_str, // <{:} (modifiers in value) + in_doc, // <<{:} (modifiers in value) + in_file, // <<< + + out_pass, // >| + out_null, // >- + out_trace, // >! + out_merge, // >& + out_str, // >{:~} (modifiers in value) + out_doc, // >>{:~} (modifiers in value) + out_file_cmp, // >>> + out_file_ovr, // >= + out_file_app // >+ + }; + + token_type () = default; + token_type (value_type v): base_type (v) {} + token_type (base_type v): base_type (v) {} + }; + + void + token_printer (ostream&, const token&, bool); + } + } +} + +#endif // BUILD2_TEST_SCRIPT_TOKEN_HXX diff --git a/build2/test/target b/build2/test/target deleted file mode 100644 index 4cbd832..0000000 --- a/build2/test/target +++ /dev/null @@ -1,29 +0,0 @@ -// file : build2/test/target -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TEST_TARGET -#define BUILD2_TEST_TARGET - -#include -#include - -#include - -namespace build2 -{ - namespace test - { - class testscript: public file - { - public: - using file::file; - - public: - static const target_type static_type; - virtual const target_type& dynamic_type () const {return static_type;} - }; - } -} - -#endif // BUILD2_TEST_TARGET diff --git a/build2/test/target.cxx b/build2/test/target.cxx index fd432fd..a85546c 100644 --- a/build2/test/target.cxx +++ b/build2/test/target.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; using namespace butl; diff --git a/build2/test/target.hxx b/build2/test/target.hxx new file mode 100644 index 0000000..9eec146 --- /dev/null +++ b/build2/test/target.hxx @@ -0,0 +1,29 @@ +// file : build2/test/target.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TEST_TARGET_HXX +#define BUILD2_TEST_TARGET_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace test + { + class testscript: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + } +} + +#endif // BUILD2_TEST_TARGET_HXX diff --git a/build2/token b/build2/token deleted file mode 100644 index d58aebd..0000000 --- a/build2/token +++ /dev/null @@ -1,186 +0,0 @@ -// file : build2/token -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TOKEN -#define BUILD2_TOKEN - -#include -#include - -#include - -namespace build2 -{ - // Extendable/inheritable enum-like class. - // - // A line consists of a sequence of words separated by separators and - // terminated with the newline. If whitespace is a separator, then it is - // ignored. - // - struct token_type - { - enum - { - // NOTE: remember to update token_printer()! - - eos, - newline, - word, - pair_separator, // token::value[0] is the pair separator char. - - colon, // : - dollar, // $ - question, // ? - comma, // , - - lparen, // ( - rparen, // ) - - lcbrace, // { - rcbrace, // } - - lsbrace, // [ - rsbrace, // ] - - assign, // = - prepend, // =+ - append, // += - - equal, // == - not_equal, // != - less, // < - greater, // > - less_equal, // <= - greater_equal, // >= - - log_or, // || - log_and, // && - log_not, // ! - - value_next - }; - - using value_type = uint16_t; - - token_type (value_type v = eos): v_ (v) {} - operator value_type () const {return v_;} - value_type v_; - }; - - // Token can be unquoted, single-quoted ('') or double-quoted (""). It can - // also be mixed. - // - enum class quote_type {unquoted, single, double_, mixed}; - - class token; - - void - token_printer (ostream&, const token&, bool); - - class token - { - public: - using printer_type = void (ostream&, const token&, bool diag); - - token_type type; - bool separated; // Whitespace-separated from the previous token. - - // Quoting can be complete, where the token starts and ends with the quote - // characters and quoting is contiguous or partial where only some part(s) - // of the token are quoted or quoting continus to the next token. - // - quote_type qtype; - bool qcomp; - - // Normally only used for word, but can also be used to store "modifiers" - // or some such for other tokens. - // - string value; - - uint64_t line; - uint64_t column; - - printer_type* printer; - - public: - token () - : token (token_type::eos, false, 0, 0, token_printer) {} - - token (token_type t, bool s, uint64_t l, uint64_t c, printer_type* p) - : token (t, string (), s, quote_type::unquoted, false, l, c, p) {} - - token (token_type t, bool s, - quote_type qt, - uint64_t l, uint64_t c, - printer_type* p) - : token (t, string (), s, qt, qt != quote_type::unquoted, l, c, p) {} - - token (string v, bool s, - quote_type qt, bool qc, - uint64_t l, uint64_t c) - : token (token_type::word, move (v), s, qt, qc, l, c, &token_printer){} - - token (token_type t, - string v, bool s, - quote_type qt, bool qc, - uint64_t l, uint64_t c, - printer_type* p) - : type (t), separated (s), - qtype (qt), qcomp (qc), - value (move (v)), - line (l), column (c), - printer (p) {} - }; - - // Output the token value in a format suitable for diagnostics. - // - inline ostream& - operator<< (ostream& o, const token& t) {t.printer (o, t, true); return o;} - - // Extendable/inheritable enum-like class. - // - struct lexer_mode_base - { - enum { value_next }; - - using value_type = uint16_t; - - lexer_mode_base (value_type v = value_next): v_ (v) {} - operator value_type () const {return v_;} - value_type v_; - }; - - struct replay_token - { - build2::token token; - const path* file; - lexer_mode_base mode; - - using location_type = build2::location; - - location_type - location () const {return location_type (file, token.line, token.column);} - }; - - using replay_tokens = vector; - - // Diagnostics plumbing. We assume that any diag stream for which we can use - // token as location has its aux data pointing to pointer to path. - // - inline location - get_location (const token& t, const path& p) - { - return location (&p, t.line, t.column); - } - - inline location - get_location (const token& t, const void* data) - { - assert (data != nullptr); // E.g., must be &parser::path_. - const path* p (*static_cast (data)); - return get_location (t, *p); - } -} - -#endif // BUILD2_TOKEN diff --git a/build2/token.cxx b/build2/token.cxx index 6f2eae5..ec78901 100644 --- a/build2/token.cxx +++ b/build2/token.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/token.hxx b/build2/token.hxx new file mode 100644 index 0000000..2a590d7 --- /dev/null +++ b/build2/token.hxx @@ -0,0 +1,186 @@ +// file : build2/token.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TOKEN_HXX +#define BUILD2_TOKEN_HXX + +#include +#include + +#include + +namespace build2 +{ + // Extendable/inheritable enum-like class. + // + // A line consists of a sequence of words separated by separators and + // terminated with the newline. If whitespace is a separator, then it is + // ignored. + // + struct token_type + { + enum + { + // NOTE: remember to update token_printer()! + + eos, + newline, + word, + pair_separator, // token::value[0] is the pair separator char. + + colon, // : + dollar, // $ + question, // ? + comma, // , + + lparen, // ( + rparen, // ) + + lcbrace, // { + rcbrace, // } + + lsbrace, // [ + rsbrace, // ] + + assign, // = + prepend, // =+ + append, // += + + equal, // == + not_equal, // != + less, // < + greater, // > + less_equal, // <= + greater_equal, // >= + + log_or, // || + log_and, // && + log_not, // ! + + value_next + }; + + using value_type = uint16_t; + + token_type (value_type v = eos): v_ (v) {} + operator value_type () const {return v_;} + value_type v_; + }; + + // Token can be unquoted, single-quoted ('') or double-quoted (""). It can + // also be mixed. + // + enum class quote_type {unquoted, single, double_, mixed}; + + class token; + + void + token_printer (ostream&, const token&, bool); + + class token + { + public: + using printer_type = void (ostream&, const token&, bool diag); + + token_type type; + bool separated; // Whitespace-separated from the previous token. + + // Quoting can be complete, where the token starts and ends with the quote + // characters and quoting is contiguous or partial where only some part(s) + // of the token are quoted or quoting continus to the next token. + // + quote_type qtype; + bool qcomp; + + // Normally only used for word, but can also be used to store "modifiers" + // or some such for other tokens. + // + string value; + + uint64_t line; + uint64_t column; + + printer_type* printer; + + public: + token () + : token (token_type::eos, false, 0, 0, token_printer) {} + + token (token_type t, bool s, uint64_t l, uint64_t c, printer_type* p) + : token (t, string (), s, quote_type::unquoted, false, l, c, p) {} + + token (token_type t, bool s, + quote_type qt, + uint64_t l, uint64_t c, + printer_type* p) + : token (t, string (), s, qt, qt != quote_type::unquoted, l, c, p) {} + + token (string v, bool s, + quote_type qt, bool qc, + uint64_t l, uint64_t c) + : token (token_type::word, move (v), s, qt, qc, l, c, &token_printer){} + + token (token_type t, + string v, bool s, + quote_type qt, bool qc, + uint64_t l, uint64_t c, + printer_type* p) + : type (t), separated (s), + qtype (qt), qcomp (qc), + value (move (v)), + line (l), column (c), + printer (p) {} + }; + + // Output the token value in a format suitable for diagnostics. + // + inline ostream& + operator<< (ostream& o, const token& t) {t.printer (o, t, true); return o;} + + // Extendable/inheritable enum-like class. + // + struct lexer_mode_base + { + enum { value_next }; + + using value_type = uint16_t; + + lexer_mode_base (value_type v = value_next): v_ (v) {} + operator value_type () const {return v_;} + value_type v_; + }; + + struct replay_token + { + build2::token token; + const path* file; + lexer_mode_base mode; + + using location_type = build2::location; + + location_type + location () const {return location_type (file, token.line, token.column);} + }; + + using replay_tokens = vector; + + // Diagnostics plumbing. We assume that any diag stream for which we can use + // token as location has its aux data pointing to pointer to path. + // + inline location + get_location (const token& t, const path& p) + { + return location (&p, t.line, t.column); + } + + inline location + get_location (const token& t, const void* data) + { + assert (data != nullptr); // E.g., must be &parser::path_. + const path* p (*static_cast (data)); + return get_location (t, *p); + } +} + +#endif // BUILD2_TOKEN_HXX diff --git a/build2/types b/build2/types deleted file mode 100644 index c189f4d..0000000 --- a/build2/types +++ /dev/null @@ -1,249 +0,0 @@ -// file : build2/types -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_TYPES -#define BUILD2_TYPES - -#include -#include -#include -#include -#include // unique_ptr, shared_ptr -#include // pair, move() -#include // size_t, nullptr_t -#include // uint{8,16,32,64}_t, *_MIN, *_MAX -#include -#include -#include // hash, function, reference_wrapper -#include - -#include -#include -#include -#include - -#include -#if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex) -# include -#endif - -#include // ios_base::failure -#include // exception -#include // logic_error, invalid_argument, runtime_error -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace build2 -{ - // Commonly-used types. - // - using std::uint8_t; - using std::uint16_t; - using std::uint32_t; - using std::uint64_t; - - using std::size_t; - using std::nullptr_t; - - using std::pair; - using std::tuple; - using std::string; - using std::function; - using std::reference_wrapper; - - using std::hash; - - using std::initializer_list; - - using std::unique_ptr; - using std::shared_ptr; - using std::weak_ptr; - - using std::array; - using std::vector; - using butl::vector_view; // - using butl::small_vector; // - - using strings = vector; - using cstrings = vector; - - using std::istream; - using std::ostream; - - // Concurrency. - // - using std::atomic; - using std::memory_order; - using std::memory_order_relaxed; - using std::memory_order_consume; - using std::memory_order_acquire; - using std::memory_order_release; - using std::memory_order_acq_rel; - using std::memory_order_seq_cst; - - using atomic_count = atomic; // Matches scheduler::atomic_count. - - using std::mutex; - using mlock = std::unique_lock; - - using std::condition_variable; - -#if defined(__cpp_lib_shared_mutex) - using shared_mutex = std::shared_mutex; - using ulock = std::unique_lock; - using slock = std::shared_lock; -#elif defined(__cpp_lib_shared_timed_mutex) - using shared_mutex = std::shared_timed_mutex; - using ulock = std::unique_lock; - using slock = std::shared_lock; -#else - // Because we have this fallback, we need to be careful not to create - // multiple shared locks in the same thread. - // - struct shared_mutex: mutex - { - using mutex::mutex; - - void lock_shared () { lock (); } - void try_lock_shared () { try_lock (); } - void unlock_shared () { unlock (); } - }; - - using ulock = std::unique_lock; - using slock = ulock; -#endif - - using std::defer_lock; - using std::adopt_lock; - - using std::thread; - namespace this_thread = std::this_thread; - - // Exceptions. - // - // While is included, there is no using for std::exception -- - // use qualified. - // - using std::logic_error; - using std::invalid_argument; - using std::runtime_error; - using std::system_error; - using io_error = std::ios_base::failure; - - // - // - using butl::optional; - using butl::nullopt; - - // - // - using butl::const_ptr; - - // - // - using butl::path; - using butl::dir_path; - using butl::basic_path; - using butl::invalid_path; - using butl::path_cast; - - // Absolute directory path. Note that for now we don't do any checking that - // the path is in fact absolute. - // - // The idea is to have a different type that we automatically complete when - // a (variable) value of this type gets initialized from untyped names. See - // value_type for details. - // - // Note that currently we also normalize and actualize the path. And we - // leave empty path as is. - // - struct abs_dir_path: dir_path - { - using dir_path::dir_path; - - explicit - abs_dir_path (dir_path d): dir_path (std::move (d)) {} - abs_dir_path () = default; - }; - - using paths = std::vector; - using dir_paths = std::vector; - - // - // - using butl::system_clock; - using butl::timestamp; - using butl::duration; - using butl::timestamp_unknown; - using butl::timestamp_unknown_rep; - using butl::timestamp_nonexistent; - using butl::operator<<; - - // - // - using butl::sha256; - - // - // - // - using butl::process; - using butl::process_path; - using butl::process_error; - - using butl::auto_fd; - using butl::ifdstream; - using butl::ofdstream; - - // - // - using butl::target_triplet; - - // - // - using butl::standard_version; - using butl::standard_version_constraint; - - // See context. - // - enum class run_phase {load, match, execute}; - - ostream& - operator<< (ostream&, run_phase); // utility.cxx - - extern run_phase phase; -} - -// In order to be found (via ADL) these have to be either in std:: or in -// butl::. The latter is a bad idea since libbutl includes the default -// implementation. They are defined in utility.cxx. -// -namespace std -{ - // Path printing with trailing slash for directories. - // - ostream& - operator<< (ostream&, const ::butl::path&); - - // Print as recall[@effect]. - // - ostream& - operator<< (ostream&, const ::butl::process_path&); -} - -// -// -#include - -#endif // BUILD2_TYPES diff --git a/build2/types-parsers b/build2/types-parsers deleted file mode 100644 index a34c2ba..0000000 --- a/build2/types-parsers +++ /dev/null @@ -1,38 +0,0 @@ -// file : build2/types-parsers -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -// CLI parsers, included into the generated source files. -// - -#ifndef BUILD2_TYPES_PARSERS -#define BUILD2_TYPES_PARSERS - -#include - -namespace build2 -{ - namespace cl - { - class scanner; - - template - struct parser; - - template <> - struct parser - { - static void - parse (path&, bool&, scanner&); - }; - - template <> - struct parser - { - static void - parse (dir_path&, bool&, scanner&); - }; - } -} - -#endif // BUILD2_TYPES_PARSERS diff --git a/build2/types-parsers.cxx b/build2/types-parsers.cxx index bcbea63..4cc723c 100644 --- a/build2/types-parsers.cxx +++ b/build2/types-parsers.cxx @@ -2,9 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include // build2::cl namespace +#include // build2::cl namespace namespace build2 { diff --git a/build2/types-parsers.hxx b/build2/types-parsers.hxx new file mode 100644 index 0000000..adbc5c3 --- /dev/null +++ b/build2/types-parsers.hxx @@ -0,0 +1,38 @@ +// file : build2/types-parsers.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +// CLI parsers, included into the generated source files. +// + +#ifndef BUILD2_TYPES_PARSERS_HXX +#define BUILD2_TYPES_PARSERS_HXX + +#include + +namespace build2 +{ + namespace cl + { + class scanner; + + template + struct parser; + + template <> + struct parser + { + static void + parse (path&, bool&, scanner&); + }; + + template <> + struct parser + { + static void + parse (dir_path&, bool&, scanner&); + }; + } +} + +#endif // BUILD2_TYPES_PARSERS_HXX diff --git a/build2/types.hxx b/build2/types.hxx new file mode 100644 index 0000000..8657ee6 --- /dev/null +++ b/build2/types.hxx @@ -0,0 +1,249 @@ +// file : build2/types.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_TYPES_HXX +#define BUILD2_TYPES_HXX + +#include +#include +#include +#include +#include // unique_ptr, shared_ptr +#include // pair, move() +#include // size_t, nullptr_t +#include // uint{8,16,32,64}_t, *_MIN, *_MAX +#include +#include +#include // hash, function, reference_wrapper +#include + +#include +#include +#include +#include + +#include +#if defined(__cpp_lib_shared_mutex) || defined(__cpp_lib_shared_timed_mutex) +# include +#endif + +#include // ios_base::failure +#include // exception +#include // logic_error, invalid_argument, runtime_error +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace build2 +{ + // Commonly-used types. + // + using std::uint8_t; + using std::uint16_t; + using std::uint32_t; + using std::uint64_t; + + using std::size_t; + using std::nullptr_t; + + using std::pair; + using std::tuple; + using std::string; + using std::function; + using std::reference_wrapper; + + using std::hash; + + using std::initializer_list; + + using std::unique_ptr; + using std::shared_ptr; + using std::weak_ptr; + + using std::array; + using std::vector; + using butl::vector_view; // + using butl::small_vector; // + + using strings = vector; + using cstrings = vector; + + using std::istream; + using std::ostream; + + // Concurrency. + // + using std::atomic; + using std::memory_order; + using std::memory_order_relaxed; + using std::memory_order_consume; + using std::memory_order_acquire; + using std::memory_order_release; + using std::memory_order_acq_rel; + using std::memory_order_seq_cst; + + using atomic_count = atomic; // Matches scheduler::atomic_count. + + using std::mutex; + using mlock = std::unique_lock; + + using std::condition_variable; + +#if defined(__cpp_lib_shared_mutex) + using shared_mutex = std::shared_mutex; + using ulock = std::unique_lock; + using slock = std::shared_lock; +#elif defined(__cpp_lib_shared_timed_mutex) + using shared_mutex = std::shared_timed_mutex; + using ulock = std::unique_lock; + using slock = std::shared_lock; +#else + // Because we have this fallback, we need to be careful not to create + // multiple shared locks in the same thread. + // + struct shared_mutex: mutex + { + using mutex::mutex; + + void lock_shared () { lock (); } + void try_lock_shared () { try_lock (); } + void unlock_shared () { unlock (); } + }; + + using ulock = std::unique_lock; + using slock = ulock; +#endif + + using std::defer_lock; + using std::adopt_lock; + + using std::thread; + namespace this_thread = std::this_thread; + + // Exceptions. + // + // While is included, there is no using for std::exception -- + // use qualified. + // + using std::logic_error; + using std::invalid_argument; + using std::runtime_error; + using std::system_error; + using io_error = std::ios_base::failure; + + // + // + using butl::optional; + using butl::nullopt; + + // + // + using butl::const_ptr; + + // + // + using butl::path; + using butl::dir_path; + using butl::basic_path; + using butl::invalid_path; + using butl::path_cast; + + // Absolute directory path. Note that for now we don't do any checking that + // the path is in fact absolute. + // + // The idea is to have a different type that we automatically complete when + // a (variable) value of this type gets initialized from untyped names. See + // value_type for details. + // + // Note that currently we also normalize and actualize the path. And we + // leave empty path as is. + // + struct abs_dir_path: dir_path + { + using dir_path::dir_path; + + explicit + abs_dir_path (dir_path d): dir_path (std::move (d)) {} + abs_dir_path () = default; + }; + + using paths = std::vector; + using dir_paths = std::vector; + + // + // + using butl::system_clock; + using butl::timestamp; + using butl::duration; + using butl::timestamp_unknown; + using butl::timestamp_unknown_rep; + using butl::timestamp_nonexistent; + using butl::operator<<; + + // + // + using butl::sha256; + + // + // + // + using butl::process; + using butl::process_path; + using butl::process_error; + + using butl::auto_fd; + using butl::ifdstream; + using butl::ofdstream; + + // + // + using butl::target_triplet; + + // + // + using butl::standard_version; + using butl::standard_version_constraint; + + // See context. + // + enum class run_phase {load, match, execute}; + + ostream& + operator<< (ostream&, run_phase); // utility.cxx + + extern run_phase phase; +} + +// In order to be found (via ADL) these have to be either in std:: or in +// butl::. The latter is a bad idea since libbutl includes the default +// implementation. They are defined in utility.cxx. +// +namespace std +{ + // Path printing with trailing slash for directories. + // + ostream& + operator<< (ostream&, const ::butl::path&); + + // Print as recall[@effect]. + // + ostream& + operator<< (ostream&, const ::butl::process_path&); +} + +// +// +#include + +#endif // BUILD2_TYPES_HXX diff --git a/build2/utility b/build2/utility deleted file mode 100644 index ca29026..0000000 --- a/build2/utility +++ /dev/null @@ -1,435 +0,0 @@ -// file : build2/utility -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_UTILITY -#define BUILD2_UTILITY - -#include // make_tuple() -#include // make_shared() -#include // to_string() -#include // move(), forward(), declval(), make_pair() -#include // assert() -#include // make_move_iterator() -#include // * -#include // ref(), cref() - -#include - -#include // combine_hash(), reverse_iterate(), case*(), etc - -#include - -#include -#include -#include - -namespace build2 -{ - using std::move; - using std::forward; - using std::declval; - - using std::ref; - using std::cref; - - using std::make_pair; - using std::make_tuple; - using std::make_shared; - using std::make_move_iterator; - using std::to_string; - using std::stoul; - using std::stoull; - - // - // - using butl::reverse_iterate; - using butl::compare_c_string; - using butl::compare_pointer_target; - //using butl::hash_pointer_target; - using butl::combine_hash; - using butl::casecmp; - using butl::case_compare_string; - using butl::case_compare_c_string; - using butl::lcase; - using butl::alpha; - using butl::alnum; - using butl::digit; - - using butl::exception_guard; - using butl::make_exception_guard; - - using butl::throw_generic_error; - - // Basic string utilities. - // - - // Trim leading/trailing whitespacec, including '\r'. - // - string& - trim (string&); - - // Find the beginning and end poistions of the next word. Return the size - // of the word or 0 and set b = e = n if there are no more words. For - // example: - // - // for (size_t b (0), e (0); next_word (s, b, e); ) - // { - // string w (s, b, e - b); - // } - // - // Or: - // - // for (size_t b (0), e (0), n; n = next_word (s, b, e, ' ', ','); ) - // { - // string w (s, b, n); - // } - // - // The second version examines up to the n'th character in the string. - // - size_t - next_word (const string&, size_t& b, size_t& e, - char d1 = ' ', char d2 = '\0'); - - size_t - next_word (const string&, size_t n, size_t& b, size_t& e, - char d1 = ' ', char d2 = '\0'); - - // Command line options. - // - extern options ops; - - // Build system driver process path (argv0.initial is argv[0]). - // - extern process_path argv0; - - // Build system driver version and check. - // - extern const standard_version build_version; - - class location; - - void - check_build_version (const standard_version_constraint&, const location&); - - // Work/home directories (must be initialized in main()) and relative path - // calculation. - // - extern dir_path work; - extern dir_path home; - - // By default this points to work. Setting this to something else should - // only be done in tightly controlled, non-concurrent situations (e.g., - // state dump). If it is empty, then relative() below returns the original - // path. - // - extern const dir_path* relative_base; - - // If possible and beneficial, translate an absolute, normalized path into - // relative to the relative_base directory, which is normally work. Note - // that if the passed path is the same as relative_base, then this function - // returns empty path. - // - template - basic_path - relative (const basic_path&); - - // In addition to calling relative(), this function also uses shorter - // notations such as '~/'. For directories the result includes the trailing - // slash. If the path is the same as base, returns "./" if current is true - // and empty string otherwise. - // - string - diag_relative (const path&, bool current = true); - - // Basic process utilities. - // - - // Start a process with the specified arguments printing the command at - // verbosity level 3 and higher. Redirect STDOUT to a pipe. If error is - // false, then redirecting STDERR to STDOUT (this can be used to suppress - // diagnostics from the child process). Issue diagnostics and throw failed - // in case of an error. - // - process_path - run_search (const char*& args0); - - process_path - run_search (const path&, bool init, const dir_path& fallback = dir_path ()); - - process - run_start (const process_path&, const char* args[], bool error); - - inline process - run_start (const char* args[], bool error) - { - return run_start (run_search (args[0]), args, error); - } - - bool - run_finish (const char* args[], bool error, process&, const string&); - - // Start the process as above and then call the specified function on each - // trimmed line of the output until it returns a non-empty object T (tested - // with T::empty()) which is then returned to the caller. - // - // The predicate can move the value out of the passed string but, if error - // is false, only in case of a "content match" (so that any diagnostics - // lines are left intact). - // - // If ignore_exit is true, then the program's exist status is ignored (if it - // is false and the program exits with the non-zero status, then an empty T - // instance is returned). - // - // If checksum is not NULL, then feed it the content of each tripped line - // (including those that come after the callback returns non-empty object). - // - template - T - run (const process_path&, - const char* args[], - F&&, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr); - - template - inline T - run (const char* args[], - F&& f, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr) - { - return run ( - run_search ( - args[0]), args, forward (f), error, ignore_exit, checksum); - } - - // run - // - template - inline T - run (const path& prog, - F&& f, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr) - { - const char* args[] = {prog.string ().c_str (), nullptr}; - return run (args, forward (f), error, ignore_exit, checksum); - } - - template - inline T - run (const process_path& pp, - F&& f, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr) - { - const char* args[] = {pp.recall_string (), nullptr}; - return run (pp, args, forward (f), error, ignore_exit, checksum); - } - - // run - // - template - inline T - run (const path& prog, - const char* arg, - F&& f, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr) - { - const char* args[] = {prog.string ().c_str (), arg, nullptr}; - return run (args, forward (f), error, ignore_exit, checksum); - } - - template - inline T - run (const process_path& pp, - const char* arg, - F&& f, - bool error = true, - bool ignore_exit = false, - sha256* checksum = nullptr) - { - const char* args[] = {pp.recall_string (), arg, nullptr}; - return run (pp, args, forward (f), error, ignore_exit, checksum); - } - - // Empty string and path. - // - extern const std::string empty_string; - extern const path empty_path; - extern const dir_path empty_dir_path; - - // Append all the values from a variable to the C-string list. T is either - // target or scope. The variable is expected to be of type strings. - // - struct variable; - - template - void - append_options (cstrings&, T&, const variable&); - - template - void - append_options (cstrings&, T&, const char*); - - template - void - append_options (strings&, T&, const variable&); - - template - void - append_options (strings&, T&, const char*); - - template - void - hash_options (sha256&, T&, const variable&); - - template - void - hash_options (sha256&, T&, const char*); - - // As above but from the strings value directly. - // - class value; - struct lookup; - - void - append_options (cstrings&, const lookup&); - - void - append_options (strings&, const lookup&); - - void - hash_options (sha256&, const lookup&); - - void - append_options (cstrings&, const strings&); - - void - append_options (strings&, const strings&); - - void - hash_options (sha256&, const strings&); - - // Check if a specified option is present in the variable or value. T is - // either target or scope. - // - template - bool - find_option (const char* option, - T&, - const variable&, - bool ignore_case = false); - - template - bool - find_option (const char* option, - T&, - const char* variable, - bool ignore_case = false); - - bool - find_option (const char* option, const lookup&, bool ignore_case = false); - - bool - find_option (const char* option, const strings&, bool ignore_case = false); - - bool - find_option (const char* option, const cstrings&, bool ignore_case = false); - - // As above but look for several options. - // - template - bool - find_options (initializer_list, - T&, - const variable&, - bool = false); - - template - bool - find_options (initializer_list, T&, const char*, bool = false); - - bool - find_options (initializer_list, const lookup&, bool = false); - - bool - find_options (initializer_list, const strings&, bool = false); - - bool - find_options (initializer_list, const cstrings&, bool = false); - - // As above but look for an option that has the specified prefix. - // - template - bool - find_option_prefix (const char* prefix, T&, const variable&, bool = false); - - template - bool - find_option_prefix (const char* prefix, T&, const char*, bool = false); - - bool - find_option_prefix (const char* prefix, const lookup&, bool = false); - - bool - find_option_prefix (const char* prefix, const strings&, bool = false); - - bool - find_option_prefix (const char* prefix, const cstrings&, bool = false); - - // As above but look for several option prefixes. - // - template - bool - find_option_prefixes (initializer_list, - T&, - const variable&, - bool = false); - - template - bool - find_option_prefixes (initializer_list, - T&, - const char*, - bool = false); - - bool - find_option_prefixes (initializer_list, - const lookup&, bool = false); - - bool - find_option_prefixes (initializer_list, - const strings&, - bool = false); - - bool - find_option_prefixes (initializer_list, - const cstrings&, - bool = false); - - // Apply the specified substitution (stem) to a '*'-pattern. If pattern - // is NULL, then return the stem itself. Assume the pattern is valid, - // i.e., contains a single '*' character. - // - string - apply_pattern (const char* stem, const string* pattern); - - // Initialize build2 global state (verbosity, home/work directories, etc). - // Should be called early in main() once. - // - void - init (const char* argv0, uint16_t verbosity); -} - -#include -#include - -#endif // BUILD2_UTILITY diff --git a/build2/utility.cxx b/build2/utility.cxx index 11c5024..5cd01be 100644 --- a/build2/utility.cxx +++ b/build2/utility.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // tzset() @@ -10,14 +10,14 @@ #include // strtol() #include // cerr -#include -#include +#include +#include using namespace std; using namespace butl; // -// +// // namespace build2 { @@ -67,7 +67,7 @@ namespace std namespace build2 { // - // + // // string& diff --git a/build2/utility.hxx b/build2/utility.hxx new file mode 100644 index 0000000..5f5aa1d --- /dev/null +++ b/build2/utility.hxx @@ -0,0 +1,436 @@ +// file : build2/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_UTILITY_HXX +#define BUILD2_UTILITY_HXX + +#include // make_tuple() +#include // make_shared() +#include // to_string() +#include // move(), forward(), declval(), make_pair() +#include // assert() +#include // make_move_iterator() +#include // * +#include // ref(), cref() + +#include + +#include // combine_hash(), reverse_iterate(), case*(), + // etc + +#include + +#include +#include +#include + +namespace build2 +{ + using std::move; + using std::forward; + using std::declval; + + using std::ref; + using std::cref; + + using std::make_pair; + using std::make_tuple; + using std::make_shared; + using std::make_move_iterator; + using std::to_string; + using std::stoul; + using std::stoull; + + // + // + using butl::reverse_iterate; + using butl::compare_c_string; + using butl::compare_pointer_target; + //using butl::hash_pointer_target; + using butl::combine_hash; + using butl::casecmp; + using butl::case_compare_string; + using butl::case_compare_c_string; + using butl::lcase; + using butl::alpha; + using butl::alnum; + using butl::digit; + + using butl::exception_guard; + using butl::make_exception_guard; + + using butl::throw_generic_error; + + // Basic string utilities. + // + + // Trim leading/trailing whitespacec, including '\r'. + // + string& + trim (string&); + + // Find the beginning and end poistions of the next word. Return the size + // of the word or 0 and set b = e = n if there are no more words. For + // example: + // + // for (size_t b (0), e (0); next_word (s, b, e); ) + // { + // string w (s, b, e - b); + // } + // + // Or: + // + // for (size_t b (0), e (0), n; n = next_word (s, b, e, ' ', ','); ) + // { + // string w (s, b, n); + // } + // + // The second version examines up to the n'th character in the string. + // + size_t + next_word (const string&, size_t& b, size_t& e, + char d1 = ' ', char d2 = '\0'); + + size_t + next_word (const string&, size_t n, size_t& b, size_t& e, + char d1 = ' ', char d2 = '\0'); + + // Command line options. + // + extern options ops; + + // Build system driver process path (argv0.initial is argv[0]). + // + extern process_path argv0; + + // Build system driver version and check. + // + extern const standard_version build_version; + + class location; + + void + check_build_version (const standard_version_constraint&, const location&); + + // Work/home directories (must be initialized in main()) and relative path + // calculation. + // + extern dir_path work; + extern dir_path home; + + // By default this points to work. Setting this to something else should + // only be done in tightly controlled, non-concurrent situations (e.g., + // state dump). If it is empty, then relative() below returns the original + // path. + // + extern const dir_path* relative_base; + + // If possible and beneficial, translate an absolute, normalized path into + // relative to the relative_base directory, which is normally work. Note + // that if the passed path is the same as relative_base, then this function + // returns empty path. + // + template + basic_path + relative (const basic_path&); + + // In addition to calling relative(), this function also uses shorter + // notations such as '~/'. For directories the result includes the trailing + // slash. If the path is the same as base, returns "./" if current is true + // and empty string otherwise. + // + string + diag_relative (const path&, bool current = true); + + // Basic process utilities. + // + + // Start a process with the specified arguments printing the command at + // verbosity level 3 and higher. Redirect STDOUT to a pipe. If error is + // false, then redirecting STDERR to STDOUT (this can be used to suppress + // diagnostics from the child process). Issue diagnostics and throw failed + // in case of an error. + // + process_path + run_search (const char*& args0); + + process_path + run_search (const path&, bool init, const dir_path& fallback = dir_path ()); + + process + run_start (const process_path&, const char* args[], bool error); + + inline process + run_start (const char* args[], bool error) + { + return run_start (run_search (args[0]), args, error); + } + + bool + run_finish (const char* args[], bool error, process&, const string&); + + // Start the process as above and then call the specified function on each + // trimmed line of the output until it returns a non-empty object T (tested + // with T::empty()) which is then returned to the caller. + // + // The predicate can move the value out of the passed string but, if error + // is false, only in case of a "content match" (so that any diagnostics + // lines are left intact). + // + // If ignore_exit is true, then the program's exist status is ignored (if it + // is false and the program exits with the non-zero status, then an empty T + // instance is returned). + // + // If checksum is not NULL, then feed it the content of each tripped line + // (including those that come after the callback returns non-empty object). + // + template + T + run (const process_path&, + const char* args[], + F&&, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr); + + template + inline T + run (const char* args[], + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + return run ( + run_search ( + args[0]), args, forward (f), error, ignore_exit, checksum); + } + + // run + // + template + inline T + run (const path& prog, + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {prog.string ().c_str (), nullptr}; + return run (args, forward (f), error, ignore_exit, checksum); + } + + template + inline T + run (const process_path& pp, + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {pp.recall_string (), nullptr}; + return run (pp, args, forward (f), error, ignore_exit, checksum); + } + + // run + // + template + inline T + run (const path& prog, + const char* arg, + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {prog.string ().c_str (), arg, nullptr}; + return run (args, forward (f), error, ignore_exit, checksum); + } + + template + inline T + run (const process_path& pp, + const char* arg, + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {pp.recall_string (), arg, nullptr}; + return run (pp, args, forward (f), error, ignore_exit, checksum); + } + + // Empty string and path. + // + extern const std::string empty_string; + extern const path empty_path; + extern const dir_path empty_dir_path; + + // Append all the values from a variable to the C-string list. T is either + // target or scope. The variable is expected to be of type strings. + // + struct variable; + + template + void + append_options (cstrings&, T&, const variable&); + + template + void + append_options (cstrings&, T&, const char*); + + template + void + append_options (strings&, T&, const variable&); + + template + void + append_options (strings&, T&, const char*); + + template + void + hash_options (sha256&, T&, const variable&); + + template + void + hash_options (sha256&, T&, const char*); + + // As above but from the strings value directly. + // + class value; + struct lookup; + + void + append_options (cstrings&, const lookup&); + + void + append_options (strings&, const lookup&); + + void + hash_options (sha256&, const lookup&); + + void + append_options (cstrings&, const strings&); + + void + append_options (strings&, const strings&); + + void + hash_options (sha256&, const strings&); + + // Check if a specified option is present in the variable or value. T is + // either target or scope. + // + template + bool + find_option (const char* option, + T&, + const variable&, + bool ignore_case = false); + + template + bool + find_option (const char* option, + T&, + const char* variable, + bool ignore_case = false); + + bool + find_option (const char* option, const lookup&, bool ignore_case = false); + + bool + find_option (const char* option, const strings&, bool ignore_case = false); + + bool + find_option (const char* option, const cstrings&, bool ignore_case = false); + + // As above but look for several options. + // + template + bool + find_options (initializer_list, + T&, + const variable&, + bool = false); + + template + bool + find_options (initializer_list, T&, const char*, bool = false); + + bool + find_options (initializer_list, const lookup&, bool = false); + + bool + find_options (initializer_list, const strings&, bool = false); + + bool + find_options (initializer_list, const cstrings&, bool = false); + + // As above but look for an option that has the specified prefix. + // + template + bool + find_option_prefix (const char* prefix, T&, const variable&, bool = false); + + template + bool + find_option_prefix (const char* prefix, T&, const char*, bool = false); + + bool + find_option_prefix (const char* prefix, const lookup&, bool = false); + + bool + find_option_prefix (const char* prefix, const strings&, bool = false); + + bool + find_option_prefix (const char* prefix, const cstrings&, bool = false); + + // As above but look for several option prefixes. + // + template + bool + find_option_prefixes (initializer_list, + T&, + const variable&, + bool = false); + + template + bool + find_option_prefixes (initializer_list, + T&, + const char*, + bool = false); + + bool + find_option_prefixes (initializer_list, + const lookup&, bool = false); + + bool + find_option_prefixes (initializer_list, + const strings&, + bool = false); + + bool + find_option_prefixes (initializer_list, + const cstrings&, + bool = false); + + // Apply the specified substitution (stem) to a '*'-pattern. If pattern + // is NULL, then return the stem itself. Assume the pattern is valid, + // i.e., contains a single '*' character. + // + string + apply_pattern (const char* stem, const string* pattern); + + // Initialize build2 global state (verbosity, home/work directories, etc). + // Should be called early in main() once. + // + void + init (const char* argv0, uint16_t verbosity); +} + +#include +#include + +#endif // BUILD2_UTILITY_HXX diff --git a/build2/variable b/build2/variable deleted file mode 100644 index be3b478..0000000 --- a/build2/variable +++ /dev/null @@ -1,1328 +0,0 @@ -// file : build2/variable -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VARIABLE -#define BUILD2_VARIABLE - -#include -#include -#include // aligned_storage -#include - -#include -#include // map_key - -#include -#include - -#include - -namespace build2 -{ - class value; - struct variable; - struct lookup; - - struct value_type - { - const char* name; // Type name for diagnostics. - const size_t size; // Type size in value::data_ (only used for PODs). - - // Base type, if any. We have very limited support for inheritance: a - // value can be cast to the base type. In particular, a derived/base value - // cannot be assigned to base/derived. If not NULL, then the cast function - // below is expected to return the base pointer if its second argument - // points to the base's value_type. - // - const value_type* base; - - // Destroy the value. If it is NULL, then the type is assumed to be POD - // with a trivial destructor. - // - void (*const dtor) (value&); - - // Copy/move constructor and copy/move assignment for data_. If NULL, then - // assume the stored data is POD. If move is true then the second argument - // can be const_cast and moved from. copy_assign() is only called with - // non-NULL first argument. - // - void (*const copy_ctor) (value&, const value&, bool move); - void (*const copy_assign) (value&, const value&, bool move); - - // While assign cannot be NULL, if append or prepend is NULL, then this - // means this type doesn't support this operation. Variable is optional - // and is provided only for diagnostics. Return true if the resulting - // value is not empty. - // - void (*const assign) (value&, names&&, const variable*); - void (*const append) (value&, names&&, const variable*); - void (*const prepend) (value&, names&&, const variable*); - - // Reverse the value back to a vector of names. Storage can be used by the - // implementation if necessary. Cannot be NULL. - // - names_view (*const reverse) (const value&, names& storage); - - // Cast value::data_ storage to value type so that the result can be - // static_cast to const T*. If it is NULL, then cast data_ directly. Note - // that this function is used for both const and non-const values. - // - const void* (*const cast) (const value&, const value_type*); - - // If NULL, then the types are compared as PODs using memcmp(). - // - int (*const compare) (const value&, const value&); - - // If NULL, then the value is never empty. - // - bool (*const empty) (const value&); - }; - - enum class variable_visibility - { - target, // Target and target type/pattern-specific. - scope, // This scope (no outer scopes). - project, // This project (no outer projects). - normal // All outer scopes. - - // Note that the search for target type/pattern-specific terminates at - // the project boundary. - }; - - // variable - // - // The two variables are considered the same if they have the same name. - // - // If the variable is overridden on the command line, then override is the - // chain of the special override variables. Their names are derived from the - // main variable name as .{__override,__prefix,__suffix} and they are - // not entered into the var_pool. The override variables only vary in their - // names and visibility. - // - // Note also that we don't propagate the variable type to override variables - // and we keep override values as untyped names. They get "typed" when they - // are applied. - // - // We use the "modify original, override on query" model. Because of that, a - // modified value does not necessarily represent the actual value so care - // must be taken to re-query after (direct) modification. And because of - // that, variables set by the C++ code are by default non-overridable. - // - // Initial processing including entering of global overrides happens in - // reset() before any other variables. Project wide overrides are entered in - // main(). Overriding happens in scope::find_override(). - // - struct variable - { - string name; - const value_type* type; // If NULL, then not (yet) typed. - unique_ptr override; - variable_visibility visibility; - size_t generation; // load_generation of this variable. - }; - - inline bool - operator== (const variable& x, const variable& y) {return x.name == y.name;} - - inline ostream& - operator<< (ostream& os, const variable& v) {return os << v.name;} - - // - // - class value - { - public: - const value_type* type; // NULL means this value is not (yet) typed. - bool null; - - // Extra data that is associated with the value that can be used to store - // flags, etc. It is initialized to 0 and copied (but not assigned) from - // one value to another but is otherwise untouched (not even when the - // value is reset to NULL). - // - // Note: if deciding to use for something make sure it is not overlapping - // with an existing usage. - // - uint16_t extra; - - explicit operator bool () const {return !null;} - bool operator== (nullptr_t) const {return null;} - bool operator!= (nullptr_t) const {return !null;} - - // Check in a type-independent way if the value is empty. The value must - // not be NULL. - // - bool - empty () const; - - // Creation. A default-initialzied value is NULL and can be reset back to - // NULL by assigning nullptr. Values can be copied and copy-assigned. Note - // that for assignment, the values' types should be the same or LHS should - // be untyped. - // - // - public: - ~value () {*this = nullptr;} - - explicit - value (nullptr_t = nullptr): type (nullptr), null (true), extra (0) {} - - explicit - value (const value_type* t): type (t), null (true), extra (0) {} - - explicit - value (names); // Create untyped value. - - template - explicit - value (T); // Create value of value_traits::value_type type. - - // Note: preserves type. - // - value& - operator= (nullptr_t) {if (!null) reset (); return *this;} - - value (value&&); - explicit value (const value&); - value& operator= (value&&); - value& operator= (const value&); - value& operator= (reference_wrapper); - value& operator= (reference_wrapper); - - // Assign/Append/Prepend. - // - public: - // Assign/append a typed value. For assign, LHS should be either of the - // same type or untyped. For append, LHS should be either of the same type - // or untyped and NULL. - // - template value& operator= (T); - template value& operator+= (T); - - template value& operator+= (T* v) { - return v != nullptr ? *this += *v : *this;} - - value& operator= (const char* v) {return *this = string (v);} - value& operator+= (const char* v) {return *this += string (v);} - - // Assign/append/prepend raw data. Variable is optional and is only used - // for diagnostics. - // - void assign (names&&, const variable*); - void assign (name&&, const variable*); // Shortcut for single name. - void append (names&&, const variable*); - void prepend (names&&, const variable*); - - - // Implementation details, don't use directly except in representation - // type implementations. - // - public: - // Fast, unchecked cast of data_ to T. - // - template T& as () & {return reinterpret_cast (data_);} - template T&& as () && {return move (as ());} - template const T& as () const& { - return reinterpret_cast (data_);} - - public: - // The maximum size we can store directly is sufficient for the most - // commonly used types (string, vector, map) on all the platforms that we - // support (each type should static assert this in its value_traits - // specialization below). Types that don't fit will have to be handled - // with an extra dynamic allocation. - // - static constexpr size_t size_ = sizeof (name_pair); - std::aligned_storage::type data_; - - // Make sure we have sufficient storage for untyped values. - // - static_assert (sizeof (names) <= size_, "insufficient space"); - - private: - void - reset (); - }; - - // This is what we call a "value pack"; it can be created by the eval - // context and passed as arguments to functions. Usually we will have just - // one value. - // - using values = small_vector; - - // The values should be of the same type (or both be untyped) except NULL - // values can also be untyped. NULL values compare equal and a NULL value - // is always less than a non-NULL. - // - bool operator== (const value&, const value&); - bool operator!= (const value&, const value&); - bool operator< (const value&, const value&); - bool operator<= (const value&, const value&); - bool operator> (const value&, const value&); - bool operator>= (const value&, const value&); - - // Value cast. The first three expect the value to be not NULL. The cast - // from lookup expects the value to aslo be defined. - // - // Note that a cast to names expects the value to be untyped while a cast - // to vector -- typed. - // - // Why are these non-members? The cast is easier on the eyes and is also - // consistent with the cast operators. The other two are for symmetry. - // - template T& cast (value&); - template T&& cast (value&&); - template const T& cast (const value&); - template const T& cast (const lookup&); - - // As above but returns NULL if the value is NULL (or not defined, in - // case of lookup). - // - template T* cast_null (value&); - template const T* cast_null (const value&); - template const T* cast_null (const lookup&); - - // As above but returns false if the value is NULL (or not defined, in case - // of lookup). Note that the template argument is only for documentation and - // should be bool (or semantically compatible). - // - template T cast_false (const value& v); - template T cast_false (const lookup& l); - - // Assign value type to the value. In the second version the variable is - // optional and is only used for diagnostics. - // - template - void typify (value&, const variable&); - void typify (value&, const value_type&, const variable*); - - // Remove value type from the value reversing it to names. This is similar - // to reverse() below except that it modifies the value itself. - // - void untypify (value&); - - // Reverse the value back to names. The value should not be NULL and storage - // should be empty. - // - vector_view - reverse (const value&, names& storage); - - vector_view - reverse (value&, names& storage); - - // lookup - // - // A variable can be undefined, NULL, or contain a (potentially empty) - // value. - // - class variable_map; - - struct lookup - { - using value_type = build2::value; - - const value_type* value; // NULL if undefined. - const variable_map* vars; // value is variable_map::value_data if not NULL. - - bool - defined () const {return value != nullptr;} - - // Note: returns true if defined and not NULL. - // - explicit operator bool () const {return defined () && !value->null;} - - const value_type& operator* () const {return *value;} - const value_type* operator-> () const {return value;} - - // Return true if this value belongs to the specified scope or target. - // Note that it can also be a target type/pattern-specific value (in - // which case it won't belong to either). - // - template - bool - belongs (const T& x) const {return vars == &x.vars;} - - lookup (): value (nullptr), vars (nullptr) {} - - template - lookup (const value_type& v, const T& x): lookup (&v, &x.vars) {} - - lookup (const value_type& v, const variable_map& vm) - : value (&v), vars (&vm) {} - - lookup (const value_type* v, const variable_map* vm) - : value (v), vars (v != nullptr ? vm : nullptr) {} - }; - - // Two lookups are equal if they point to the same variable. - // - inline bool - operator== (const lookup& x, const lookup& y) - { - bool r (x.value == y.value); - assert (!r || x.vars == y.vars); - return r; - } - - inline bool - operator!= (const lookup& x, const lookup& y) {return !(x == y);} - - - // Representation types. - // - // Potential optimizations: - // - // - Split value::operator=/+=() into const T and T&&, also overload - // value_traits functions that they call. - // - // - Specialization for vector (if used and becomes critical). - // - template - struct value_traits_specialization; // enable_if'able specialization support. - - template - struct value_traits: value_traits_specialization {}; - // { - // static_assert (sizeof (T) <= value::size_, "insufficient space"); - // - // // Convert name to T. If rhs is not NULL, then it is the second half - // // of a pair. Only needs to be provided by simple types. Throw - // // invalid_argument (with a message) if the name is not a valid - // // representation of value (in which case the name should remain - // // unchanged for diagnostics). - // // - // static T convert (name&&, name* rhs); - // - // // Assign/append/prepend T to value which is already of type T but can - // // be NULL. - // // - // static void assign (value&, T&&); - // static void append (value&, T&&); - // static void prepend (value&, T&&); - // - // // Reverse a value back to name. Only needs to be provided by simple - // // types. - // // - // static name reverse (const T&); - // - // // Compare two values. Only needs to be provided by simple types. - // // - // static int compare (const T&, const T&); - // - // // Return true if the value is empty. - // // - // static bool empty (const T&); - // - // // True if can be constructed from empty names as T(). - // // - // static const bool empty_value = true; - // - // // For simple types (those that can be used as elements of containers), - // // type_name must be constexpr in order to sidestep the static init - // // order issue (in fact, that's the only reason we have it both here - // // and in value_type.name -- value_type cannot be constexpr because - // // of pointers to function template instantiations). - // // - // static const char* const type_name; - // static const build2::value_type value_type; - // }; - - // Convert name to a simple value. Throw invalid_argument (with a message) - // if the name is not a valid representation of value (in which case the - // name remains unchanged for diagnostics). The second version is called for - // a pair. - // - template T convert (name&&); - template T convert (name&&, name&&); - - // As above but can also be called for container types. Note that in this - // case (container) if invalid_argument is thrown, the names are not - // guaranteed to be unchanged. - // - //template T convert (names&&); (declaration causes ambiguity) - - // Convert value to T. If value is already of type T, then simply cast it. - // Otherwise call convert(names) above. - // - template T convert (value&&); - - // Default implementations of the dtor/copy_ctor/copy_assing callbacks for - // types that are stored directly in value::data_ and the provide all the - // necessary functions (copy/move ctor and assignment operator). - // - template - static void - default_dtor (value&); - - template - static void - default_copy_ctor (value&, const value&, bool); - - template - static void - default_copy_assign (value&, const value&, bool); - - // Default implementations of the empty callback that calls - // value_traits::empty(). - // - template - static bool - default_empty (const value&); - - // Default implementations of the assign/append/prepend callbacks for simple - // types. They call value_traits::convert() and then pass the result to - // value_traits::assign()/append()/prepend(). As a result, it may not be - // the most efficient way to do it. - // - template - static void - simple_assign (value&, names&&, const variable*); - - template - static void - simple_append (value&, names&&, const variable*); - - template - static void - simple_prepend (value&, names&&, const variable*); - - // Default implementations of the reverse callback for simple types that - // calls value_traits::reverse() and adds the result to the vector. As a - // result, it may not be the most efficient way to do it. - // - template - static names_view - simple_reverse (const value&, names&); - - // Default implementations of the compare callback for simple types that - // calls value_traits::compare(). - // - template - static int - simple_compare (const value&, const value&); - - // bool - // - template <> - struct value_traits - { - static_assert (sizeof (bool) <= value::size_, "insufficient space"); - - static bool convert (name&&, name*); - static void assign (value&, bool); - static void append (value&, bool); // OR. - static name reverse (bool x) {return name (x ? "true" : "false");} - static int compare (bool, bool); - static bool empty (bool) {return false;} - - static const bool empty_value = false; - static const char* const type_name; - static const build2::value_type value_type; - }; - - template <> - struct value_traits - { - static_assert (sizeof (uint64_t) <= value::size_, "insufficient space"); - - static uint64_t convert (name&&, name*); - static void assign (value&, uint64_t); - static void append (value&, uint64_t); // ADD. - static name reverse (uint64_t x) {return name (to_string (x));} - static int compare (uint64_t, uint64_t); - static bool empty (bool) {return false;} - - static const bool empty_value = false; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // Treat unsigned integral types as uint64. Note that bool is handled - // differently at an earlier stage. - // - template - struct value_traits_specialization::value && - std::is_unsigned::value>::type>: - value_traits {}; - - // string - // - template <> - struct value_traits - { - static_assert (sizeof (string) <= value::size_, "insufficient space"); - - static string convert (name&&, name*); - static void assign (value&, string&&); - static void append (value&, string&&); - static void prepend (value&, string&&); - static name reverse (const string& x) {return name (x);} - static int compare (const string&, const string&); - static bool empty (const string& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // Treat const char* as string. - // - template <> - struct value_traits: value_traits {}; - - // path - // - template <> - struct value_traits - { - static_assert (sizeof (path) <= value::size_, "insufficient space"); - - static path convert (name&&, name*); - static void assign (value&, path&&); - static void append (value&, path&&); // operator/ - static void prepend (value&, path&&); // operator/ - static name reverse (const path& x) { - return x.to_directory () - ? name (path_cast (x)) - : name (x.string ()); - } - static int compare (const path&, const path&); - static bool empty (const path& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // dir_path - // - template <> - struct value_traits - { - static_assert (sizeof (dir_path) <= value::size_, "insufficient space"); - - static dir_path convert (name&&, name*); - static void assign (value&, dir_path&&); - static void append (value&, dir_path&&); // operator/ - static void prepend (value&, dir_path&&); // operator/ - static name reverse (const dir_path& x) {return name (x);} - static int compare (const dir_path&, const dir_path&); - static bool empty (const dir_path& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // abs_dir_path - // - template <> - struct value_traits - { - static_assert (sizeof (abs_dir_path) <= value::size_, - "insufficient space"); - - static abs_dir_path convert (name&&, name*); - static void assign (value&, abs_dir_path&&); - static void append (value&, abs_dir_path&&); // operator/ - static name reverse (const abs_dir_path& x) {return name (x);} - static int compare (const abs_dir_path&, const abs_dir_path&); - static bool empty (const abs_dir_path& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // name - // - template <> - struct value_traits - { - static_assert (sizeof (name) <= value::size_, "insufficient space"); - - static name convert (name&&, name*); - static void assign (value&, name&&); - static name reverse (const name& x) {return x;} - static int compare (const name& l, const name& r) {return l.compare (r);} - static bool empty (const name& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // name_pair - // - // An empty first or second half of a pair is treated as unspecified (this - // way it can be usage-specific whether a single value is first or second - // half of a pair). If both are empty then this is an empty value (and not a - // pair of two empties). - // - template <> - struct value_traits - { - static_assert (sizeof (name_pair) <= value::size_, "insufficient space"); - - static name_pair convert (name&&, name*); - static void assign (value&, name_pair&&); - static int compare (const name_pair&, const name_pair&); - static bool empty (const name_pair& x) { - return x.first.empty () && x.second.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // process_path - // - // Note that instances that we store always have non-empty recall and - // initial is its shallow copy. - // - template <> - struct value_traits - { - static_assert (sizeof (process_path) <= value::size_, - "insufficient space"); - - // This one is represented as a @-pair of names. As a result it cannot - // be stored in a container. - // - static process_path convert (name&&, name*); - static void assign (value&, process_path&&); - static int compare (const process_path&, const process_path&); - static bool empty (const process_path& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - - // target_triplet - // - template <> - struct value_traits - { - static_assert (sizeof (target_triplet) <= value::size_, - "insufficient space"); - - static target_triplet convert (name&&, name*); - static void assign (value&, target_triplet&&); - static name reverse (const target_triplet& x) {return name (x.string ());} - static int compare (const target_triplet& x, const target_triplet& y) { - return x.compare (y);} - static bool empty (const target_triplet& x) {return x.empty ();} - - static const bool empty_value = true; - static const char* const type_name; - static const build2::value_type value_type; - }; - - // vector - // - template - struct value_traits> - { - static_assert (sizeof (vector) <= value::size_, "insufficient space"); - - static vector convert (names&&); - static void assign (value&, vector&&); - static void append (value&, vector&&); - static void prepend (value&, vector&&); - static bool empty (const vector& x) {return x.empty ();} - - // Make sure these are static-initialized together. Failed that VC will - // make sure it's done in the wrong order. - // - struct value_type_ex: build2::value_type - { - string type_name; - value_type_ex (value_type&&); - }; - static const value_type_ex value_type; - }; - - // map - // - template - struct value_traits> - { - template using map = std::map; - - static_assert (sizeof (map) <= value::size_, "insufficient space"); - - static void assign (value&, map&&); - static void append (value&, map&&); - static void prepend (value& v, map&& x) { - return append (v, move (x));} - static bool empty (const map& x) {return x.empty ();} - - // Make sure these are static-initialized together. Failed that VC will - // make sure it's done in the wrong order. - // - struct value_type_ex: build2::value_type - { - string type_name; - value_type_ex (value_type&&); - }; - static const value_type_ex value_type; - }; - - // Project-wide (as opposed to global) variable overrides. Returned by - // context.cxx:reset(). - // - struct variable_override - { - const variable& var; // Original variable. - const variable& ovr; // Override variable. - value val; - }; - - using variable_overrides = vector; - - // Variable pool. - // - // The global version is protected by the model mutex. - // - class variable_pool - { - public: - // Find existing or insert new. - // - const variable& - operator[] (const string& name) const; - - // Return NULL if there is no variable with this name. - // - const variable* - find (const string& name) const; - - // Find existing or insert new (untyped, non-overridable, normal - // visibility; but may be overridden by a pattern). - // - const variable& - insert (string name) - { - return insert (move (name), nullptr, nullptr, nullptr); - } - - // Insert or override (type/visibility). Note that by default the - // variable is not overridable. - // - const variable& - insert (string name, variable_visibility v) - { - return insert (move (name), nullptr, &v, nullptr); - } - - const variable& - insert (string name, bool overridable) - { - return insert (move (name), nullptr, nullptr, &overridable); - } - - const variable& - insert (string name, bool overridable, variable_visibility v) - { - return insert (move (name), nullptr, &v, &overridable); - } - - template - const variable& - insert (string name) - { - return insert (move (name), &value_traits::value_type); - } - - template - const variable& - insert (string name, variable_visibility v) - { - return insert (move (name), &value_traits::value_type, &v); - } - - template - const variable& - insert (string name, bool overridable) - { - return insert ( - move (name), &value_traits::value_type, nullptr, &overridable); - } - - template - const variable& - insert (string name, bool overridable, variable_visibility v) - { - return insert ( - move (name), &value_traits::value_type, &v, &overridable); - } - - // Insert a variable pattern. Any variable that matches this pattern - // will have the specified type, visibility, and overridability. If - // match is true, then individual insertions of the matching variable - // must match the specified type/visibility/overridability. Otherwise, - // individual insertions can provide alternative values and the pattern - // values are a fallback (if you specify false you better be very clear - // about what you are trying to achieve). - // - // The pattern must be in the form [.](*|**)[.] where - // '*' matches single component stems (i.e., 'foo' but not 'foo.bar') - // and '**' matches single and multi-component stems. Note that only - // multi-component variables are considered for pattern matching (so - // just '*' won't match anything). - // - // The patterns are matched in the more-specific-first order where the - // pattern is considered more specific if it has a greater sum of its - // prefix and suffix lengths. If the prefix and suffix are equal, then the - // '*' pattern is considered more specific than '**'. If neither is more - // specific, then they are matched in the reverse order of insertion. - // - // If retro is true then a newly inserted pattern is also applied - // retrospectively to all the existing variables that match but only - // if no more specific pattern already exists (which is then assumed - // to have been applied). So if you use this functionality, watch out - // for the insertion order (you probably want more specific first). - // - public: - void - insert_pattern (const string& pattern, - optional type, - optional overridable, - optional, - bool retro = false, - bool match = true); - - template - void - insert_pattern (const string& p, - optional overridable, - optional v, - bool retro = false, - bool match = true) - { - insert_pattern ( - p, &value_traits::value_type, overridable, v, retro, match); - } - - public: - void - clear () {map_.clear ();} - - variable_pool (): variable_pool (false) {} - - // RW access. - // - variable_pool& - rw () const - { - assert (phase == run_phase::load); - return const_cast (*this); - } - - variable_pool& - rw (scope&) const {return const_cast (*this);} - - private: - static variable_pool instance; - - const variable& - insert (string name, - const value_type*, - const variable_visibility* = nullptr, - const bool* overridable = nullptr); - - void - update (variable&, - const value_type*, - const variable_visibility* = nullptr, - const bool* = nullptr) const; - - // Entities that can access bypassing the lock proof. - // - friend class parser; - friend class scope; - friend variable_overrides reset (const strings&); - - public: - static const variable_pool& cinstance; // For var_pool initialization. - - // Variable map. - // - private: - using key = butl::map_key; - using map = std::unordered_map; - - pair - insert (variable&& var) - { - // Keeping a pointer to the key while moving things during insertion is - // tricky. We could use a C-string instead of C++ for a key but that - // gets hairy very quickly (there is no std::hash for C-strings). So - // let's rely on small object-optimized std::string for now. - // - string n (var.name); - auto r (map_.insert (map::value_type (&n, move (var)))); - - if (r.second) - r.first->first.p = &r.first->second.name; - - return r; - } - - map map_; - - // Patterns. - // - public: - struct pattern - { - string prefix; - string suffix; - bool multi; // Match multi-component stems. - bool match; // Must match individual variable insersions. - - optional type; - optional visibility; - optional overridable; - - friend bool - operator< (const pattern& x, const pattern& y) - { - if (x.prefix.size () + x.suffix.size () < - y.prefix.size () + y.suffix.size ()) - return true; - - if (x.prefix == y.prefix && x.suffix == y.suffix) - return x.multi && !y.multi; - - return false; - } - }; - - private: - std::multiset patterns_; - - // Global pool flag. - // - private: - explicit - variable_pool (bool global): global_ (global) {} - - bool global_; - }; - - extern const variable_pool& var_pool; -} - -// variable_map -// -namespace butl -{ - template <> - struct compare_prefix>: - compare_prefix - { - typedef compare_prefix base; - - explicit - compare_prefix (char d): base (d) {} - - bool - operator() (const build2::variable& x, const build2::variable& y) const - { - return base::operator() (x.name, y.name); - } - - bool - prefix (const build2::variable& p, const build2::variable& k) const - { - return base::prefix (p.name, k.name); - } - }; -} - -namespace build2 -{ - class variable_map - { - public: - struct value_data: value - { - using value::value; - using value::operator=; - - size_t version = 0; // Incremented on each modification (variable_cache). - size_t generation; // load_generation of this value (global state only). - }; - - using map_type = butl::prefix_map, - value_data, - '.'>; - using size_type = map_type::size_type; - - template - class iterator_adapter: public I - { - public: - iterator_adapter () = default; - iterator_adapter (const I& i, const variable_map& m): I (i), m_ (&m) {} - - // Automatically type a newly typed value on access. - // - typename I::reference operator* () const; - typename I::pointer operator-> () const; - - // Untyped access. - // - uint16_t extra () const {return I::operator* ().second.extra;} - typename I::reference untyped () const {return I::operator* ();} - - private: - const variable_map* m_; - }; - - using const_iterator = iterator_adapter; - - // Lookup. Note that variable overrides will not be applied, even if - // set in this map. - // - lookup - operator[] (const variable& var) const - { - return lookup (find (var), this); - } - - lookup - operator[] (const variable* var) const // For cached variables. - { - assert (var != nullptr); - return operator[] (*var); - } - - lookup - operator[] (const string& name) const - { - const variable* var (var_pool.find (name)); - return var != nullptr ? operator[] (*var) : lookup (); - } - - // If typed is false, leave the value untyped even if the variable is. - // - const value_data* - find (const variable&, bool typed = true) const; - - value_data* - find_to_modify (const variable&, bool typed = true); - - // Convert a lookup pointing to a value belonging to this variable map - // to its non-const version. Note that this is only safe on the original - // values (see find_original()). - // - value& - modify (const lookup& l) - { - assert (l.vars == this); - value& r (const_cast (*l.value)); - static_cast (r).version++; - return r; - } - - // Return a value suitable for assignment. See scope for details. - // - value& - assign (const variable& var) {return insert (var).first;} - - // Note that the variable is expected to have already been registered. - // - value& - assign (const string& name) {return insert (var_pool[name]).first;} - - // As above but also return an indication of whether the new value (which - // will be NULL) was actually inserted. Similar to find(), if typed is - // false, leave the value untyped even if the variable is. - // - pair, bool> - insert (const variable&, bool typed = true); - - pair - find_namespace (const variable& ns) const - { - auto r (m_.find_prefix (ns)); - return make_pair (const_iterator (r.first, *this), - const_iterator (r.second, *this)); - } - - const_iterator - begin () const {return const_iterator (m_.begin (), *this);} - - const_iterator - end () const {return const_iterator (m_.end (), *this);} - - bool - empty () const {return m_.empty ();} - - size_type - size () const {return m_.size ();} - - public: - // Global should be true if this map is part of the global (model) state - // (e.g., scopes, etc). - // - explicit - variable_map (bool global = false): global_ (global) {} - - private: - friend class variable_type_map; - - void - typify (value_data&, const variable&) const; - - private: - bool global_; - map_type m_; - }; - - // Value caching. Used for overrides as well as target type/pattern-specific - // append/prepend. - // - // In many places we assume that we can store a reference to the returned - // variable value (e.g., install::lookup_install()). As a result, in these - // cases where we calculate the value dynamically, we have to cache it - // (note, however, that if the value becomes stale, there is no guarantee - // the references remain valid). - // - // Note that since the cache can be modified on any lookup (including during - // the execute phase), it is protected by its own mutex shard (allocated in - // main()). - // - extern size_t variable_cache_mutex_shard_size; - extern unique_ptr variable_cache_mutex_shard; - - template - class variable_cache - { - public: - // If the returned unique lock is locked, then the value has been - // invalidated. If the variable type does not match the value type, - // then typify the cached value. - // - pair - insert (K, const lookup& stem, size_t version, const variable&); - - private: - struct entry_type - { - // Note: we use value_data instead of value since the result is often - // returned as lookup. We also maintain the version in case one cached - // value (e.g., override) is based on another (e.g., target - // type/pattern-specific prepend/append). - // - variable_map::value_data value; - - size_t version = 0; // Version on which this value is based. - - // Location of the stem as well as the version on which this cache - // value is based. Used to track the location and value of the stem - // for cache invalidation. NULL/0 means there is no stem. - // - const variable_map* stem_vars = nullptr; - size_t stem_version = 0; - - // For GCC 4.9. - // - entry_type () = default; - entry_type (variable_map::value_data val, - size_t ver, - const variable_map* svars, - size_t sver) - : value (move (val)), - version (ver), - stem_vars (svars), - stem_version (sver) {} - }; - - using map_type = std::map; - - map_type m_; - }; - - // Target type/pattern-specific variables. - // - class variable_pattern_map - { - public: - using map_type = std::map; - using const_iterator = map_type::const_iterator; - using const_reverse_iterator = map_type::const_reverse_iterator; - - explicit - variable_pattern_map (bool global): global_ (global) {} - - variable_map& - operator[] (const string& v) - { - return map_.emplace (v, variable_map (global_)).first->second; - } - - const_iterator begin () const {return map_.begin ();} - const_iterator end () const {return map_.end ();} - const_reverse_iterator rbegin () const {return map_.rbegin ();} - const_reverse_iterator rend () const {return map_.rend ();} - bool empty () const {return map_.empty ();} - - private: - bool global_; - map_type map_; - }; - - class variable_type_map - { - public: - using map_type = std::map, - variable_pattern_map>; - using const_iterator = map_type::const_iterator; - - explicit - variable_type_map (bool global): global_ (global) {} - - variable_pattern_map& - operator[] (const target_type& t) - { - return map_.emplace (t, variable_pattern_map (global_)).first->second; - } - - const_iterator begin () const {return map_.begin ();} - const_iterator end () const {return map_.end ();} - bool empty () const {return map_.empty ();} - - lookup - find (const target_type&, const string& tname, const variable&) const; - - // Prepend/append value cache. - // - // The key is the combination of the "original value identity" (as a - // pointer to the value in one of the variable_pattern_map's) and the - // "target identity" (as target type and target name). Note that while at - // first it may seem like we don't need the target identity, we actually - // do since the stem may itself be target-type/pattern-specific. See - // scope::find_original() for details. - // - mutable - variable_cache> - cache; - - private: - bool global_; - map_type map_; - }; -} - -#include -#include - -#endif // BUILD2_VARIABLE diff --git a/build2/variable.cxx b/build2/variable.cxx index f282dc5..bcf9d62 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -2,12 +2,12 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include #include // memcmp() -#include -#include +#include +#include using namespace std; diff --git a/build2/variable.hxx b/build2/variable.hxx new file mode 100644 index 0000000..19a6c69 --- /dev/null +++ b/build2/variable.hxx @@ -0,0 +1,1328 @@ +// file : build2/variable.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VARIABLE_HXX +#define BUILD2_VARIABLE_HXX + +#include +#include +#include // aligned_storage +#include + +#include +#include // map_key + +#include +#include + +#include + +namespace build2 +{ + class value; + struct variable; + struct lookup; + + struct value_type + { + const char* name; // Type name for diagnostics. + const size_t size; // Type size in value::data_ (only used for PODs). + + // Base type, if any. We have very limited support for inheritance: a + // value can be cast to the base type. In particular, a derived/base value + // cannot be assigned to base/derived. If not NULL, then the cast function + // below is expected to return the base pointer if its second argument + // points to the base's value_type. + // + const value_type* base; + + // Destroy the value. If it is NULL, then the type is assumed to be POD + // with a trivial destructor. + // + void (*const dtor) (value&); + + // Copy/move constructor and copy/move assignment for data_. If NULL, then + // assume the stored data is POD. If move is true then the second argument + // can be const_cast and moved from. copy_assign() is only called with + // non-NULL first argument. + // + void (*const copy_ctor) (value&, const value&, bool move); + void (*const copy_assign) (value&, const value&, bool move); + + // While assign cannot be NULL, if append or prepend is NULL, then this + // means this type doesn't support this operation. Variable is optional + // and is provided only for diagnostics. Return true if the resulting + // value is not empty. + // + void (*const assign) (value&, names&&, const variable*); + void (*const append) (value&, names&&, const variable*); + void (*const prepend) (value&, names&&, const variable*); + + // Reverse the value back to a vector of names. Storage can be used by the + // implementation if necessary. Cannot be NULL. + // + names_view (*const reverse) (const value&, names& storage); + + // Cast value::data_ storage to value type so that the result can be + // static_cast to const T*. If it is NULL, then cast data_ directly. Note + // that this function is used for both const and non-const values. + // + const void* (*const cast) (const value&, const value_type*); + + // If NULL, then the types are compared as PODs using memcmp(). + // + int (*const compare) (const value&, const value&); + + // If NULL, then the value is never empty. + // + bool (*const empty) (const value&); + }; + + enum class variable_visibility + { + target, // Target and target type/pattern-specific. + scope, // This scope (no outer scopes). + project, // This project (no outer projects). + normal // All outer scopes. + + // Note that the search for target type/pattern-specific terminates at + // the project boundary. + }; + + // variable + // + // The two variables are considered the same if they have the same name. + // + // If the variable is overridden on the command line, then override is the + // chain of the special override variables. Their names are derived from the + // main variable name as .{__override,__prefix,__suffix} and they are + // not entered into the var_pool. The override variables only vary in their + // names and visibility. + // + // Note also that we don't propagate the variable type to override variables + // and we keep override values as untyped names. They get "typed" when they + // are applied. + // + // We use the "modify original, override on query" model. Because of that, a + // modified value does not necessarily represent the actual value so care + // must be taken to re-query after (direct) modification. And because of + // that, variables set by the C++ code are by default non-overridable. + // + // Initial processing including entering of global overrides happens in + // reset() before any other variables. Project wide overrides are entered in + // main(). Overriding happens in scope::find_override(). + // + struct variable + { + string name; + const value_type* type; // If NULL, then not (yet) typed. + unique_ptr override; + variable_visibility visibility; + size_t generation; // load_generation of this variable. + }; + + inline bool + operator== (const variable& x, const variable& y) {return x.name == y.name;} + + inline ostream& + operator<< (ostream& os, const variable& v) {return os << v.name;} + + // + // + class value + { + public: + const value_type* type; // NULL means this value is not (yet) typed. + bool null; + + // Extra data that is associated with the value that can be used to store + // flags, etc. It is initialized to 0 and copied (but not assigned) from + // one value to another but is otherwise untouched (not even when the + // value is reset to NULL). + // + // Note: if deciding to use for something make sure it is not overlapping + // with an existing usage. + // + uint16_t extra; + + explicit operator bool () const {return !null;} + bool operator== (nullptr_t) const {return null;} + bool operator!= (nullptr_t) const {return !null;} + + // Check in a type-independent way if the value is empty. The value must + // not be NULL. + // + bool + empty () const; + + // Creation. A default-initialzied value is NULL and can be reset back to + // NULL by assigning nullptr. Values can be copied and copy-assigned. Note + // that for assignment, the values' types should be the same or LHS should + // be untyped. + // + // + public: + ~value () {*this = nullptr;} + + explicit + value (nullptr_t = nullptr): type (nullptr), null (true), extra (0) {} + + explicit + value (const value_type* t): type (t), null (true), extra (0) {} + + explicit + value (names); // Create untyped value. + + template + explicit + value (T); // Create value of value_traits::value_type type. + + // Note: preserves type. + // + value& + operator= (nullptr_t) {if (!null) reset (); return *this;} + + value (value&&); + explicit value (const value&); + value& operator= (value&&); + value& operator= (const value&); + value& operator= (reference_wrapper); + value& operator= (reference_wrapper); + + // Assign/Append/Prepend. + // + public: + // Assign/append a typed value. For assign, LHS should be either of the + // same type or untyped. For append, LHS should be either of the same type + // or untyped and NULL. + // + template value& operator= (T); + template value& operator+= (T); + + template value& operator+= (T* v) { + return v != nullptr ? *this += *v : *this;} + + value& operator= (const char* v) {return *this = string (v);} + value& operator+= (const char* v) {return *this += string (v);} + + // Assign/append/prepend raw data. Variable is optional and is only used + // for diagnostics. + // + void assign (names&&, const variable*); + void assign (name&&, const variable*); // Shortcut for single name. + void append (names&&, const variable*); + void prepend (names&&, const variable*); + + + // Implementation details, don't use directly except in representation + // type implementations. + // + public: + // Fast, unchecked cast of data_ to T. + // + template T& as () & {return reinterpret_cast (data_);} + template T&& as () && {return move (as ());} + template const T& as () const& { + return reinterpret_cast (data_);} + + public: + // The maximum size we can store directly is sufficient for the most + // commonly used types (string, vector, map) on all the platforms that we + // support (each type should static assert this in its value_traits + // specialization below). Types that don't fit will have to be handled + // with an extra dynamic allocation. + // + static constexpr size_t size_ = sizeof (name_pair); + std::aligned_storage::type data_; + + // Make sure we have sufficient storage for untyped values. + // + static_assert (sizeof (names) <= size_, "insufficient space"); + + private: + void + reset (); + }; + + // This is what we call a "value pack"; it can be created by the eval + // context and passed as arguments to functions. Usually we will have just + // one value. + // + using values = small_vector; + + // The values should be of the same type (or both be untyped) except NULL + // values can also be untyped. NULL values compare equal and a NULL value + // is always less than a non-NULL. + // + bool operator== (const value&, const value&); + bool operator!= (const value&, const value&); + bool operator< (const value&, const value&); + bool operator<= (const value&, const value&); + bool operator> (const value&, const value&); + bool operator>= (const value&, const value&); + + // Value cast. The first three expect the value to be not NULL. The cast + // from lookup expects the value to aslo be defined. + // + // Note that a cast to names expects the value to be untyped while a cast + // to vector -- typed. + // + // Why are these non-members? The cast is easier on the eyes and is also + // consistent with the cast operators. The other two are for symmetry. + // + template T& cast (value&); + template T&& cast (value&&); + template const T& cast (const value&); + template const T& cast (const lookup&); + + // As above but returns NULL if the value is NULL (or not defined, in + // case of lookup). + // + template T* cast_null (value&); + template const T* cast_null (const value&); + template const T* cast_null (const lookup&); + + // As above but returns false if the value is NULL (or not defined, in case + // of lookup). Note that the template argument is only for documentation and + // should be bool (or semantically compatible). + // + template T cast_false (const value& v); + template T cast_false (const lookup& l); + + // Assign value type to the value. In the second version the variable is + // optional and is only used for diagnostics. + // + template + void typify (value&, const variable&); + void typify (value&, const value_type&, const variable*); + + // Remove value type from the value reversing it to names. This is similar + // to reverse() below except that it modifies the value itself. + // + void untypify (value&); + + // Reverse the value back to names. The value should not be NULL and storage + // should be empty. + // + vector_view + reverse (const value&, names& storage); + + vector_view + reverse (value&, names& storage); + + // lookup + // + // A variable can be undefined, NULL, or contain a (potentially empty) + // value. + // + class variable_map; + + struct lookup + { + using value_type = build2::value; + + const value_type* value; // NULL if undefined. + const variable_map* vars; // value is variable_map::value_data if not NULL. + + bool + defined () const {return value != nullptr;} + + // Note: returns true if defined and not NULL. + // + explicit operator bool () const {return defined () && !value->null;} + + const value_type& operator* () const {return *value;} + const value_type* operator-> () const {return value;} + + // Return true if this value belongs to the specified scope or target. + // Note that it can also be a target type/pattern-specific value (in + // which case it won't belong to either). + // + template + bool + belongs (const T& x) const {return vars == &x.vars;} + + lookup (): value (nullptr), vars (nullptr) {} + + template + lookup (const value_type& v, const T& x): lookup (&v, &x.vars) {} + + lookup (const value_type& v, const variable_map& vm) + : value (&v), vars (&vm) {} + + lookup (const value_type* v, const variable_map* vm) + : value (v), vars (v != nullptr ? vm : nullptr) {} + }; + + // Two lookups are equal if they point to the same variable. + // + inline bool + operator== (const lookup& x, const lookup& y) + { + bool r (x.value == y.value); + assert (!r || x.vars == y.vars); + return r; + } + + inline bool + operator!= (const lookup& x, const lookup& y) {return !(x == y);} + + + // Representation types. + // + // Potential optimizations: + // + // - Split value::operator=/+=() into const T and T&&, also overload + // value_traits functions that they call. + // + // - Specialization for vector (if used and becomes critical). + // + template + struct value_traits_specialization; // enable_if'able specialization support. + + template + struct value_traits: value_traits_specialization {}; + // { + // static_assert (sizeof (T) <= value::size_, "insufficient space"); + // + // // Convert name to T. If rhs is not NULL, then it is the second half + // // of a pair. Only needs to be provided by simple types. Throw + // // invalid_argument (with a message) if the name is not a valid + // // representation of value (in which case the name should remain + // // unchanged for diagnostics). + // // + // static T convert (name&&, name* rhs); + // + // // Assign/append/prepend T to value which is already of type T but can + // // be NULL. + // // + // static void assign (value&, T&&); + // static void append (value&, T&&); + // static void prepend (value&, T&&); + // + // // Reverse a value back to name. Only needs to be provided by simple + // // types. + // // + // static name reverse (const T&); + // + // // Compare two values. Only needs to be provided by simple types. + // // + // static int compare (const T&, const T&); + // + // // Return true if the value is empty. + // // + // static bool empty (const T&); + // + // // True if can be constructed from empty names as T(). + // // + // static const bool empty_value = true; + // + // // For simple types (those that can be used as elements of containers), + // // type_name must be constexpr in order to sidestep the static init + // // order issue (in fact, that's the only reason we have it both here + // // and in value_type.name -- value_type cannot be constexpr because + // // of pointers to function template instantiations). + // // + // static const char* const type_name; + // static const build2::value_type value_type; + // }; + + // Convert name to a simple value. Throw invalid_argument (with a message) + // if the name is not a valid representation of value (in which case the + // name remains unchanged for diagnostics). The second version is called for + // a pair. + // + template T convert (name&&); + template T convert (name&&, name&&); + + // As above but can also be called for container types. Note that in this + // case (container) if invalid_argument is thrown, the names are not + // guaranteed to be unchanged. + // + //template T convert (names&&); (declaration causes ambiguity) + + // Convert value to T. If value is already of type T, then simply cast it. + // Otherwise call convert(names) above. + // + template T convert (value&&); + + // Default implementations of the dtor/copy_ctor/copy_assing callbacks for + // types that are stored directly in value::data_ and the provide all the + // necessary functions (copy/move ctor and assignment operator). + // + template + static void + default_dtor (value&); + + template + static void + default_copy_ctor (value&, const value&, bool); + + template + static void + default_copy_assign (value&, const value&, bool); + + // Default implementations of the empty callback that calls + // value_traits::empty(). + // + template + static bool + default_empty (const value&); + + // Default implementations of the assign/append/prepend callbacks for simple + // types. They call value_traits::convert() and then pass the result to + // value_traits::assign()/append()/prepend(). As a result, it may not be + // the most efficient way to do it. + // + template + static void + simple_assign (value&, names&&, const variable*); + + template + static void + simple_append (value&, names&&, const variable*); + + template + static void + simple_prepend (value&, names&&, const variable*); + + // Default implementations of the reverse callback for simple types that + // calls value_traits::reverse() and adds the result to the vector. As a + // result, it may not be the most efficient way to do it. + // + template + static names_view + simple_reverse (const value&, names&); + + // Default implementations of the compare callback for simple types that + // calls value_traits::compare(). + // + template + static int + simple_compare (const value&, const value&); + + // bool + // + template <> + struct value_traits + { + static_assert (sizeof (bool) <= value::size_, "insufficient space"); + + static bool convert (name&&, name*); + static void assign (value&, bool); + static void append (value&, bool); // OR. + static name reverse (bool x) {return name (x ? "true" : "false");} + static int compare (bool, bool); + static bool empty (bool) {return false;} + + static const bool empty_value = false; + static const char* const type_name; + static const build2::value_type value_type; + }; + + template <> + struct value_traits + { + static_assert (sizeof (uint64_t) <= value::size_, "insufficient space"); + + static uint64_t convert (name&&, name*); + static void assign (value&, uint64_t); + static void append (value&, uint64_t); // ADD. + static name reverse (uint64_t x) {return name (to_string (x));} + static int compare (uint64_t, uint64_t); + static bool empty (bool) {return false;} + + static const bool empty_value = false; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // Treat unsigned integral types as uint64. Note that bool is handled + // differently at an earlier stage. + // + template + struct value_traits_specialization::value && + std::is_unsigned::value>::type>: + value_traits {}; + + // string + // + template <> + struct value_traits + { + static_assert (sizeof (string) <= value::size_, "insufficient space"); + + static string convert (name&&, name*); + static void assign (value&, string&&); + static void append (value&, string&&); + static void prepend (value&, string&&); + static name reverse (const string& x) {return name (x);} + static int compare (const string&, const string&); + static bool empty (const string& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // Treat const char* as string. + // + template <> + struct value_traits: value_traits {}; + + // path + // + template <> + struct value_traits + { + static_assert (sizeof (path) <= value::size_, "insufficient space"); + + static path convert (name&&, name*); + static void assign (value&, path&&); + static void append (value&, path&&); // operator/ + static void prepend (value&, path&&); // operator/ + static name reverse (const path& x) { + return x.to_directory () + ? name (path_cast (x)) + : name (x.string ()); + } + static int compare (const path&, const path&); + static bool empty (const path& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // dir_path + // + template <> + struct value_traits + { + static_assert (sizeof (dir_path) <= value::size_, "insufficient space"); + + static dir_path convert (name&&, name*); + static void assign (value&, dir_path&&); + static void append (value&, dir_path&&); // operator/ + static void prepend (value&, dir_path&&); // operator/ + static name reverse (const dir_path& x) {return name (x);} + static int compare (const dir_path&, const dir_path&); + static bool empty (const dir_path& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // abs_dir_path + // + template <> + struct value_traits + { + static_assert (sizeof (abs_dir_path) <= value::size_, + "insufficient space"); + + static abs_dir_path convert (name&&, name*); + static void assign (value&, abs_dir_path&&); + static void append (value&, abs_dir_path&&); // operator/ + static name reverse (const abs_dir_path& x) {return name (x);} + static int compare (const abs_dir_path&, const abs_dir_path&); + static bool empty (const abs_dir_path& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // name + // + template <> + struct value_traits + { + static_assert (sizeof (name) <= value::size_, "insufficient space"); + + static name convert (name&&, name*); + static void assign (value&, name&&); + static name reverse (const name& x) {return x;} + static int compare (const name& l, const name& r) {return l.compare (r);} + static bool empty (const name& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // name_pair + // + // An empty first or second half of a pair is treated as unspecified (this + // way it can be usage-specific whether a single value is first or second + // half of a pair). If both are empty then this is an empty value (and not a + // pair of two empties). + // + template <> + struct value_traits + { + static_assert (sizeof (name_pair) <= value::size_, "insufficient space"); + + static name_pair convert (name&&, name*); + static void assign (value&, name_pair&&); + static int compare (const name_pair&, const name_pair&); + static bool empty (const name_pair& x) { + return x.first.empty () && x.second.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // process_path + // + // Note that instances that we store always have non-empty recall and + // initial is its shallow copy. + // + template <> + struct value_traits + { + static_assert (sizeof (process_path) <= value::size_, + "insufficient space"); + + // This one is represented as a @-pair of names. As a result it cannot + // be stored in a container. + // + static process_path convert (name&&, name*); + static void assign (value&, process_path&&); + static int compare (const process_path&, const process_path&); + static bool empty (const process_path& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + + // target_triplet + // + template <> + struct value_traits + { + static_assert (sizeof (target_triplet) <= value::size_, + "insufficient space"); + + static target_triplet convert (name&&, name*); + static void assign (value&, target_triplet&&); + static name reverse (const target_triplet& x) {return name (x.string ());} + static int compare (const target_triplet& x, const target_triplet& y) { + return x.compare (y);} + static bool empty (const target_triplet& x) {return x.empty ();} + + static const bool empty_value = true; + static const char* const type_name; + static const build2::value_type value_type; + }; + + // vector + // + template + struct value_traits> + { + static_assert (sizeof (vector) <= value::size_, "insufficient space"); + + static vector convert (names&&); + static void assign (value&, vector&&); + static void append (value&, vector&&); + static void prepend (value&, vector&&); + static bool empty (const vector& x) {return x.empty ();} + + // Make sure these are static-initialized together. Failed that VC will + // make sure it's done in the wrong order. + // + struct value_type_ex: build2::value_type + { + string type_name; + value_type_ex (value_type&&); + }; + static const value_type_ex value_type; + }; + + // map + // + template + struct value_traits> + { + template using map = std::map; + + static_assert (sizeof (map) <= value::size_, "insufficient space"); + + static void assign (value&, map&&); + static void append (value&, map&&); + static void prepend (value& v, map&& x) { + return append (v, move (x));} + static bool empty (const map& x) {return x.empty ();} + + // Make sure these are static-initialized together. Failed that VC will + // make sure it's done in the wrong order. + // + struct value_type_ex: build2::value_type + { + string type_name; + value_type_ex (value_type&&); + }; + static const value_type_ex value_type; + }; + + // Project-wide (as opposed to global) variable overrides. Returned by + // context.cxx:reset(). + // + struct variable_override + { + const variable& var; // Original variable. + const variable& ovr; // Override variable. + value val; + }; + + using variable_overrides = vector; + + // Variable pool. + // + // The global version is protected by the model mutex. + // + class variable_pool + { + public: + // Find existing or insert new. + // + const variable& + operator[] (const string& name) const; + + // Return NULL if there is no variable with this name. + // + const variable* + find (const string& name) const; + + // Find existing or insert new (untyped, non-overridable, normal + // visibility; but may be overridden by a pattern). + // + const variable& + insert (string name) + { + return insert (move (name), nullptr, nullptr, nullptr); + } + + // Insert or override (type/visibility). Note that by default the + // variable is not overridable. + // + const variable& + insert (string name, variable_visibility v) + { + return insert (move (name), nullptr, &v, nullptr); + } + + const variable& + insert (string name, bool overridable) + { + return insert (move (name), nullptr, nullptr, &overridable); + } + + const variable& + insert (string name, bool overridable, variable_visibility v) + { + return insert (move (name), nullptr, &v, &overridable); + } + + template + const variable& + insert (string name) + { + return insert (move (name), &value_traits::value_type); + } + + template + const variable& + insert (string name, variable_visibility v) + { + return insert (move (name), &value_traits::value_type, &v); + } + + template + const variable& + insert (string name, bool overridable) + { + return insert ( + move (name), &value_traits::value_type, nullptr, &overridable); + } + + template + const variable& + insert (string name, bool overridable, variable_visibility v) + { + return insert ( + move (name), &value_traits::value_type, &v, &overridable); + } + + // Insert a variable pattern. Any variable that matches this pattern + // will have the specified type, visibility, and overridability. If + // match is true, then individual insertions of the matching variable + // must match the specified type/visibility/overridability. Otherwise, + // individual insertions can provide alternative values and the pattern + // values are a fallback (if you specify false you better be very clear + // about what you are trying to achieve). + // + // The pattern must be in the form [.](*|**)[.] where + // '*' matches single component stems (i.e., 'foo' but not 'foo.bar') + // and '**' matches single and multi-component stems. Note that only + // multi-component variables are considered for pattern matching (so + // just '*' won't match anything). + // + // The patterns are matched in the more-specific-first order where the + // pattern is considered more specific if it has a greater sum of its + // prefix and suffix lengths. If the prefix and suffix are equal, then the + // '*' pattern is considered more specific than '**'. If neither is more + // specific, then they are matched in the reverse order of insertion. + // + // If retro is true then a newly inserted pattern is also applied + // retrospectively to all the existing variables that match but only + // if no more specific pattern already exists (which is then assumed + // to have been applied). So if you use this functionality, watch out + // for the insertion order (you probably want more specific first). + // + public: + void + insert_pattern (const string& pattern, + optional type, + optional overridable, + optional, + bool retro = false, + bool match = true); + + template + void + insert_pattern (const string& p, + optional overridable, + optional v, + bool retro = false, + bool match = true) + { + insert_pattern ( + p, &value_traits::value_type, overridable, v, retro, match); + } + + public: + void + clear () {map_.clear ();} + + variable_pool (): variable_pool (false) {} + + // RW access. + // + variable_pool& + rw () const + { + assert (phase == run_phase::load); + return const_cast (*this); + } + + variable_pool& + rw (scope&) const {return const_cast (*this);} + + private: + static variable_pool instance; + + const variable& + insert (string name, + const value_type*, + const variable_visibility* = nullptr, + const bool* overridable = nullptr); + + void + update (variable&, + const value_type*, + const variable_visibility* = nullptr, + const bool* = nullptr) const; + + // Entities that can access bypassing the lock proof. + // + friend class parser; + friend class scope; + friend variable_overrides reset (const strings&); + + public: + static const variable_pool& cinstance; // For var_pool initialization. + + // Variable map. + // + private: + using key = butl::map_key; + using map = std::unordered_map; + + pair + insert (variable&& var) + { + // Keeping a pointer to the key while moving things during insertion is + // tricky. We could use a C-string instead of C++ for a key but that + // gets hairy very quickly (there is no std::hash for C-strings). So + // let's rely on small object-optimized std::string for now. + // + string n (var.name); + auto r (map_.insert (map::value_type (&n, move (var)))); + + if (r.second) + r.first->first.p = &r.first->second.name; + + return r; + } + + map map_; + + // Patterns. + // + public: + struct pattern + { + string prefix; + string suffix; + bool multi; // Match multi-component stems. + bool match; // Must match individual variable insersions. + + optional type; + optional visibility; + optional overridable; + + friend bool + operator< (const pattern& x, const pattern& y) + { + if (x.prefix.size () + x.suffix.size () < + y.prefix.size () + y.suffix.size ()) + return true; + + if (x.prefix == y.prefix && x.suffix == y.suffix) + return x.multi && !y.multi; + + return false; + } + }; + + private: + std::multiset patterns_; + + // Global pool flag. + // + private: + explicit + variable_pool (bool global): global_ (global) {} + + bool global_; + }; + + extern const variable_pool& var_pool; +} + +// variable_map +// +namespace butl +{ + template <> + struct compare_prefix>: + compare_prefix + { + typedef compare_prefix base; + + explicit + compare_prefix (char d): base (d) {} + + bool + operator() (const build2::variable& x, const build2::variable& y) const + { + return base::operator() (x.name, y.name); + } + + bool + prefix (const build2::variable& p, const build2::variable& k) const + { + return base::prefix (p.name, k.name); + } + }; +} + +namespace build2 +{ + class variable_map + { + public: + struct value_data: value + { + using value::value; + using value::operator=; + + size_t version = 0; // Incremented on each modification (variable_cache). + size_t generation; // load_generation of this value (global state only). + }; + + using map_type = butl::prefix_map, + value_data, + '.'>; + using size_type = map_type::size_type; + + template + class iterator_adapter: public I + { + public: + iterator_adapter () = default; + iterator_adapter (const I& i, const variable_map& m): I (i), m_ (&m) {} + + // Automatically type a newly typed value on access. + // + typename I::reference operator* () const; + typename I::pointer operator-> () const; + + // Untyped access. + // + uint16_t extra () const {return I::operator* ().second.extra;} + typename I::reference untyped () const {return I::operator* ();} + + private: + const variable_map* m_; + }; + + using const_iterator = iterator_adapter; + + // Lookup. Note that variable overrides will not be applied, even if + // set in this map. + // + lookup + operator[] (const variable& var) const + { + return lookup (find (var), this); + } + + lookup + operator[] (const variable* var) const // For cached variables. + { + assert (var != nullptr); + return operator[] (*var); + } + + lookup + operator[] (const string& name) const + { + const variable* var (var_pool.find (name)); + return var != nullptr ? operator[] (*var) : lookup (); + } + + // If typed is false, leave the value untyped even if the variable is. + // + const value_data* + find (const variable&, bool typed = true) const; + + value_data* + find_to_modify (const variable&, bool typed = true); + + // Convert a lookup pointing to a value belonging to this variable map + // to its non-const version. Note that this is only safe on the original + // values (see find_original()). + // + value& + modify (const lookup& l) + { + assert (l.vars == this); + value& r (const_cast (*l.value)); + static_cast (r).version++; + return r; + } + + // Return a value suitable for assignment. See scope for details. + // + value& + assign (const variable& var) {return insert (var).first;} + + // Note that the variable is expected to have already been registered. + // + value& + assign (const string& name) {return insert (var_pool[name]).first;} + + // As above but also return an indication of whether the new value (which + // will be NULL) was actually inserted. Similar to find(), if typed is + // false, leave the value untyped even if the variable is. + // + pair, bool> + insert (const variable&, bool typed = true); + + pair + find_namespace (const variable& ns) const + { + auto r (m_.find_prefix (ns)); + return make_pair (const_iterator (r.first, *this), + const_iterator (r.second, *this)); + } + + const_iterator + begin () const {return const_iterator (m_.begin (), *this);} + + const_iterator + end () const {return const_iterator (m_.end (), *this);} + + bool + empty () const {return m_.empty ();} + + size_type + size () const {return m_.size ();} + + public: + // Global should be true if this map is part of the global (model) state + // (e.g., scopes, etc). + // + explicit + variable_map (bool global = false): global_ (global) {} + + private: + friend class variable_type_map; + + void + typify (value_data&, const variable&) const; + + private: + bool global_; + map_type m_; + }; + + // Value caching. Used for overrides as well as target type/pattern-specific + // append/prepend. + // + // In many places we assume that we can store a reference to the returned + // variable value (e.g., install::lookup_install()). As a result, in these + // cases where we calculate the value dynamically, we have to cache it + // (note, however, that if the value becomes stale, there is no guarantee + // the references remain valid). + // + // Note that since the cache can be modified on any lookup (including during + // the execute phase), it is protected by its own mutex shard (allocated in + // main()). + // + extern size_t variable_cache_mutex_shard_size; + extern unique_ptr variable_cache_mutex_shard; + + template + class variable_cache + { + public: + // If the returned unique lock is locked, then the value has been + // invalidated. If the variable type does not match the value type, + // then typify the cached value. + // + pair + insert (K, const lookup& stem, size_t version, const variable&); + + private: + struct entry_type + { + // Note: we use value_data instead of value since the result is often + // returned as lookup. We also maintain the version in case one cached + // value (e.g., override) is based on another (e.g., target + // type/pattern-specific prepend/append). + // + variable_map::value_data value; + + size_t version = 0; // Version on which this value is based. + + // Location of the stem as well as the version on which this cache + // value is based. Used to track the location and value of the stem + // for cache invalidation. NULL/0 means there is no stem. + // + const variable_map* stem_vars = nullptr; + size_t stem_version = 0; + + // For GCC 4.9. + // + entry_type () = default; + entry_type (variable_map::value_data val, + size_t ver, + const variable_map* svars, + size_t sver) + : value (move (val)), + version (ver), + stem_vars (svars), + stem_version (sver) {} + }; + + using map_type = std::map; + + map_type m_; + }; + + // Target type/pattern-specific variables. + // + class variable_pattern_map + { + public: + using map_type = std::map; + using const_iterator = map_type::const_iterator; + using const_reverse_iterator = map_type::const_reverse_iterator; + + explicit + variable_pattern_map (bool global): global_ (global) {} + + variable_map& + operator[] (const string& v) + { + return map_.emplace (v, variable_map (global_)).first->second; + } + + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + const_reverse_iterator rbegin () const {return map_.rbegin ();} + const_reverse_iterator rend () const {return map_.rend ();} + bool empty () const {return map_.empty ();} + + private: + bool global_; + map_type map_; + }; + + class variable_type_map + { + public: + using map_type = std::map, + variable_pattern_map>; + using const_iterator = map_type::const_iterator; + + explicit + variable_type_map (bool global): global_ (global) {} + + variable_pattern_map& + operator[] (const target_type& t) + { + return map_.emplace (t, variable_pattern_map (global_)).first->second; + } + + const_iterator begin () const {return map_.begin ();} + const_iterator end () const {return map_.end ();} + bool empty () const {return map_.empty ();} + + lookup + find (const target_type&, const string& tname, const variable&) const; + + // Prepend/append value cache. + // + // The key is the combination of the "original value identity" (as a + // pointer to the value in one of the variable_pattern_map's) and the + // "target identity" (as target type and target name). Note that while at + // first it may seem like we don't need the target identity, we actually + // do since the stem may itself be target-type/pattern-specific. See + // scope::find_original() for details. + // + mutable + variable_cache> + cache; + + private: + bool global_; + map_type map_; + }; +} + +#include +#include + +#endif // BUILD2_VARIABLE_HXX diff --git a/build2/variable.txx b/build2/variable.txx index c7d3e5a..1373948 100644 --- a/build2/variable.txx +++ b/build2/variable.txx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include namespace build2 { diff --git a/build2/version-impl b/build2/version-impl deleted file mode 100644 index f4c132d..0000000 --- a/build2/version-impl +++ /dev/null @@ -1,18 +0,0 @@ -// file : build2/version-impl -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION // Note: using the version macro itself. - -// This is a "fake" version used to bootstrap from the repository (we include -// a pre-generated version in distributions). -// -#define BUILD2_VERSION 9999999990000ULL - -#define BUILD2_VERSION_STR "999.999.999" -#define BUILD2_VERSION_ID "999.999.999" - -#define LIBBUTL_VERSION_STR "999.999.999" -#define LIBBUTL_VERSION_ID "999.999.999" - -#endif // BUILD2_VERSION diff --git a/build2/version-impl.in b/build2/version-impl.in deleted file mode 100644 index a514240..0000000 --- a/build2/version-impl.in +++ /dev/null @@ -1,44 +0,0 @@ -// file : build2/version-impl.in -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION // Note: using the version macro itself. - -// Note: using build2 standard versioning scheme. The numeric version format -// is AAABBBCCCDDDE where: -// -// AAA - major version number -// BBB - minor version number -// CCC - bugfix version number -// DDD - alpha / beta (DDD + 500) version number -// E - final (0) / snapshot (1) -// -// When DDDE is not 0, 1 is subtracted from AAABBBCCC. For example: -// -// Version AAABBBCCCDDDE -// -// 0.1.0 0000010000000 -// 0.1.2 0000010010000 -// 1.2.3 0010020030000 -// 2.2.0-a.1 0020019990010 -// 3.0.0-b.2 0029999995020 -// 2.2.0-a.1.z 0020019990011 -// -#define BUILD2_VERSION $build2.version.project_number$ULL -#define BUILD2_VERSION_STR "$build2.version.project$" -#define BUILD2_VERSION_ID "$build2.version.project_id$" - -#define BUILD2_VERSION_MAJOR $build2.version.major$ -#define BUILD2_VERSION_MINOR $build2.version.minor$ -#define BUILD2_VERSION_PATCH $build2.version.patch$ - -#define BUILD2_PRE_RELEASE $build2.version.pre_release$ - -#define BUILD2_SNAPSHOT $build2.version.snapshot_sn$ULL -#define BUILD2_SNAPSHOT_ID "$build2.version.snapshot_id$" - -#include - -$libbutl.check(LIBBUTL_VERSION, LIBBUTL_SNAPSHOT)$ - -#endif // BUILD2_VERSION diff --git a/build2/version.hxx.in b/build2/version.hxx.in new file mode 100644 index 0000000..c05a207 --- /dev/null +++ b/build2/version.hxx.in @@ -0,0 +1,44 @@ +// file : build2/version.hxx.in -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION // Note: using the version macro itself. + +// Note: using build2 standard versioning scheme. The numeric version format +// is AAABBBCCCDDDE where: +// +// AAA - major version number +// BBB - minor version number +// CCC - bugfix version number +// DDD - alpha / beta (DDD + 500) version number +// E - final (0) / snapshot (1) +// +// When DDDE is not 0, 1 is subtracted from AAABBBCCC. For example: +// +// Version AAABBBCCCDDDE +// +// 0.1.0 0000010000000 +// 0.1.2 0000010010000 +// 1.2.3 0010020030000 +// 2.2.0-a.1 0020019990010 +// 3.0.0-b.2 0029999995020 +// 2.2.0-a.1.z 0020019990011 +// +#define BUILD2_VERSION $build2.version.project_number$ULL +#define BUILD2_VERSION_STR "$build2.version.project$" +#define BUILD2_VERSION_ID "$build2.version.project_id$" + +#define BUILD2_VERSION_MAJOR $build2.version.major$ +#define BUILD2_VERSION_MINOR $build2.version.minor$ +#define BUILD2_VERSION_PATCH $build2.version.patch$ + +#define BUILD2_PRE_RELEASE $build2.version.pre_release$ + +#define BUILD2_SNAPSHOT $build2.version.snapshot_sn$ULL +#define BUILD2_SNAPSHOT_ID "$build2.version.snapshot_id$" + +#include + +$libbutl.check(LIBBUTL_VERSION, LIBBUTL_SNAPSHOT)$ + +#endif // BUILD2_VERSION diff --git a/build2/version/init b/build2/version/init deleted file mode 100644 index 8ffb999..0000000 --- a/build2/version/init +++ /dev/null @@ -1,31 +0,0 @@ -// file : build2/version/init -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_INIT -#define BUILD2_VERSION_INIT - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - void - boot (scope&, const location&, unique_ptr&); - - bool - init (scope&, - scope&, - const location&, - unique_ptr&, - bool, - bool, - const variable_map&); - } -} - -#endif // BUILD2_VERSION_INIT diff --git a/build2/version/init.cxx b/build2/version/init.cxx index cce8de9..699495a 100644 --- a/build2/version/init.cxx +++ b/build2/version/init.cxx @@ -2,21 +2,21 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include -#include +#include +#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/build2/version/init.hxx b/build2/version/init.hxx new file mode 100644 index 0000000..54beae1 --- /dev/null +++ b/build2/version/init.hxx @@ -0,0 +1,31 @@ +// file : build2/version/init.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION_INIT_HXX +#define BUILD2_VERSION_INIT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + void + boot (scope&, const location&, unique_ptr&); + + bool + init (scope&, + scope&, + const location&, + unique_ptr&, + bool, + bool, + const variable_map&); + } +} + +#endif // BUILD2_VERSION_INIT_HXX diff --git a/build2/version/module b/build2/version/module deleted file mode 100644 index 1e6fb7a..0000000 --- a/build2/version/module +++ /dev/null @@ -1,36 +0,0 @@ -// file : build2/version/module -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_MODULE -#define BUILD2_VERSION_MODULE - -#include - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - // The 'depends' values from manifest. - // - using dependency_constraints = std::map; - - struct module: module_base - { - static const string name; - - butl::standard_version version; - dependency_constraints dependencies; - - module (butl::standard_version v, dependency_constraints d) - : version (move (v)), dependencies (move (d)) {} - }; - } -} - -#endif // BUILD2_VERSION_MODULE diff --git a/build2/version/module.cxx b/build2/version/module.cxx index 9865271..acc629a 100644 --- a/build2/version/module.cxx +++ b/build2/version/module.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/version/module.hxx b/build2/version/module.hxx new file mode 100644 index 0000000..45b1a47 --- /dev/null +++ b/build2/version/module.hxx @@ -0,0 +1,36 @@ +// file : build2/version/module.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION_MODULE_HXX +#define BUILD2_VERSION_MODULE_HXX + +#include + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + // The 'depends' values from manifest. + // + using dependency_constraints = std::map; + + struct module: module_base + { + static const string name; + + butl::standard_version version; + dependency_constraints dependencies; + + module (butl::standard_version v, dependency_constraints d) + : version (move (v)), dependencies (move (d)) {} + }; + } +} + +#endif // BUILD2_VERSION_MODULE_HXX diff --git a/build2/version/rule b/build2/version/rule deleted file mode 100644 index 186df59..0000000 --- a/build2/version/rule +++ /dev/null @@ -1,53 +0,0 @@ -// file : build2/version/rule -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_RULE -#define BUILD2_VERSION_RULE - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - // Generate a version file. - // - class version_doc: public rule - { - public: - version_doc () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static target_state - perform_update (action, const target&); - }; - - // Preprocess an .in file. - // - class version_in: public rule - { - public: - version_in () {} - - virtual match_result - match (action, target&, const string&) const override; - - virtual recipe - apply (action, target&) const override; - - static target_state - perform_update (action, const target&); - }; - } -} - -#endif // BUILD2_VERSION_RULE diff --git a/build2/version/rule.cxx b/build2/version/rule.cxx index 3e143c1..d9669da 100644 --- a/build2/version/rule.cxx +++ b/build2/version/rule.cxx @@ -2,18 +2,18 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include using namespace std; using namespace butl; diff --git a/build2/version/rule.hxx b/build2/version/rule.hxx new file mode 100644 index 0000000..e686694 --- /dev/null +++ b/build2/version/rule.hxx @@ -0,0 +1,53 @@ +// file : build2/version/rule.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION_RULE_HXX +#define BUILD2_VERSION_RULE_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + // Generate a version file. + // + class version_doc: public rule + { + public: + version_doc () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + }; + + // Preprocess an .in file. + // + class version_in: public rule + { + public: + version_in () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + + static target_state + perform_update (action, const target&); + }; + } +} + +#endif // BUILD2_VERSION_RULE_HXX diff --git a/build2/version/snapshot b/build2/version/snapshot deleted file mode 100644 index a279729..0000000 --- a/build2/version/snapshot +++ /dev/null @@ -1,33 +0,0 @@ -// file : build2/version/snapshot -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#ifndef BUILD2_VERSION_SNAPSHOT -#define BUILD2_VERSION_SNAPSHOT - -#include -#include - -#include - -namespace build2 -{ - namespace version - { - struct snapshot - { - uint64_t sn = 0; - string id; - - bool - empty () const {return sn == 0;} - }; - - // Return empty snapshot if unknown scm or uncommitted. - // - snapshot - extract_snapshot (const scope& rs); - } -} - -#endif // BUILD2_VERSION_SNAPSHOT diff --git a/build2/version/snapshot-git.cxx b/build2/version/snapshot-git.cxx index b5db470..d91eb62 100644 --- a/build2/version/snapshot-git.cxx +++ b/build2/version/snapshot-git.cxx @@ -2,7 +2,7 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include using namespace std; diff --git a/build2/version/snapshot.cxx b/build2/version/snapshot.cxx index be6c147..00528c7 100644 --- a/build2/version/snapshot.cxx +++ b/build2/version/snapshot.cxx @@ -2,9 +2,9 @@ // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file -#include +#include -#include +#include using namespace std; diff --git a/build2/version/snapshot.hxx b/build2/version/snapshot.hxx new file mode 100644 index 0000000..b7e6dd7 --- /dev/null +++ b/build2/version/snapshot.hxx @@ -0,0 +1,33 @@ +// file : build2/version/snapshot.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD2_VERSION_SNAPSHOT_HXX +#define BUILD2_VERSION_SNAPSHOT_HXX + +#include +#include + +#include + +namespace build2 +{ + namespace version + { + struct snapshot + { + uint64_t sn = 0; + string id; + + bool + empty () const {return sn == 0;} + }; + + // Return empty snapshot if unknown scm or uncommitted. + // + snapshot + extract_snapshot (const scope& rs); + } +} + +#endif // BUILD2_VERSION_SNAPSHOT_HXX diff --git a/old-tests/depdb/driver.cxx b/old-tests/depdb/driver.cxx index b64b97e..2439b60 100644 --- a/old-tests/depdb/driver.cxx +++ b/old-tests/depdb/driver.cxx @@ -4,12 +4,12 @@ #include -#include +#include -#include -#include +#include +#include -#include +#include using namespace std; using namespace build2; diff --git a/tests/.gitignore b/tests/.gitignore index e54525b..eac2bb8 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,6 @@ driver + +# The immediate test/ sub-directory shouldn't be ignored. +# +*/test/ +test-*/ diff --git a/tests/build/root.build b/tests/build/root.build index 1903c41..7d483fe 100644 --- a/tests/build/root.build +++ b/tests/build/root.build @@ -6,7 +6,7 @@ cxx.std = latest using cxx -hxx{*}: extension = +hxx{*}: extension = hxx cxx{*}: extension = cxx # Setup the build system driver that we are testing (which may not be the same diff --git a/tests/test/script/runner/driver.cxx b/tests/test/script/runner/driver.cxx index ff91926..b31acb7 100644 --- a/tests/test/script/runner/driver.cxx +++ b/tests/test/script/runner/driver.cxx @@ -11,10 +11,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include using namespace std; using namespace butl; diff --git a/unit-tests/.gitignore b/unit-tests/.gitignore index e54525b..eac2bb8 100644 --- a/unit-tests/.gitignore +++ b/unit-tests/.gitignore @@ -1 +1,6 @@ driver + +# The immediate test/ sub-directory shouldn't be ignored. +# +*/test/ +test-*/ diff --git a/unit-tests/function/driver.cxx b/unit-tests/function/driver.cxx index 70fd69e..4650a18 100644 --- a/unit-tests/function/driver.cxx +++ b/unit-tests/function/driver.cxx @@ -4,14 +4,14 @@ #include -#include -#include - -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include +#include using namespace std; diff --git a/unit-tests/lexer/driver.cxx b/unit-tests/lexer/driver.cxx index 41366ec..5bbefb9 100644 --- a/unit-tests/lexer/driver.cxx +++ b/unit-tests/lexer/driver.cxx @@ -5,11 +5,11 @@ #include #include -#include -#include +#include +#include -#include -#include +#include +#include using namespace std; diff --git a/unit-tests/scheduler/driver.cxx b/unit-tests/scheduler/driver.cxx index 2e20937..31551f2 100644 --- a/unit-tests/scheduler/driver.cxx +++ b/unit-tests/scheduler/driver.cxx @@ -8,10 +8,10 @@ #include #include -#include -#include +#include +#include -#include +#include using namespace std; diff --git a/unit-tests/test/script/lexer/driver.cxx b/unit-tests/test/script/lexer/driver.cxx index 229c3b6..9ff393b 100644 --- a/unit-tests/test/script/lexer/driver.cxx +++ b/unit-tests/test/script/lexer/driver.cxx @@ -5,11 +5,11 @@ #include #include -#include -#include +#include +#include -#include -#include +#include +#include using namespace std; diff --git a/unit-tests/test/script/parser/directive.test b/unit-tests/test/script/parser/directive.test index d735fb4..39bd666 100644 --- a/unit-tests/test/script/parser/directive.test +++ b/unit-tests/test/script/parser/directive.test @@ -46,11 +46,11 @@ EOI : var-expansion : -cat <="foo-$(build.version).test"; +cat <="foo-$(build.verson.project).test"; cmd EOI $* <>EOO -.include "foo-$(build.version).test" +.include "foo-$(build.verson.project).test" EOI cmd EOO diff --git a/unit-tests/test/script/parser/driver.cxx b/unit-tests/test/script/parser/driver.cxx index 772bc10..fceb477 100644 --- a/unit-tests/test/script/parser/driver.cxx +++ b/unit-tests/test/script/parser/driver.cxx @@ -5,18 +5,18 @@ #include #include -#include -#include +#include +#include -#include -#include // reset() -#include +#include +#include // reset() +#include -#include +#include -#include -#include -#include +#include +#include +#include using namespace std; diff --git a/unit-tests/test/script/regex/driver.cxx b/unit-tests/test/script/regex/driver.cxx index 2680672..fd35f2a 100644 --- a/unit-tests/test/script/regex/driver.cxx +++ b/unit-tests/test/script/regex/driver.cxx @@ -5,7 +5,7 @@ #include #include // is_pod, is_array -#include +#include using namespace std; using namespace build2::test::script::regex; -- cgit v1.1