/** @defgroup Build Build system framework * * @brief A build system framework for build tools. * * This is a framework that can be used to create elaborate build systems * as well as simple standalone tools. * * @{ */ /** @file make.hh * * @brief A dependency tracker core similar to Make. * * This implements a dependency tracking module that is essentially Make * but without any Make syntax or shell invocations. It is extensible and * can be adapted to many kinds of scenarios. * * @copyright See COPYING.md in the project tree for further information. */ #ifndef OSTD_BUILD_MAKE_HH #define OSTD_BUILD_MAKE_HH #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ostd { namespace build { /** @addtogroup Build * @{ */ struct make_error: std::runtime_error { using std::runtime_error::runtime_error; template make_error(string_range fmt, A const &...args): make_error( ostd::format(ostd::appender(), fmt, args...).get() ) {} }; struct OSTD_EXPORT make_pattern { make_pattern() = delete; make_pattern(string_range target): p_target(target) {} std::pair match(string_range target); std::string replace(string_range dep) const; private: std::string p_target; std::vector p_subs{}; }; inline auto make_depend_simple(string_range dep) { return [d = std::string{dep}]( string_range, decltype(appender>()) &app ) { /* need to copy as it may be called multiple times */ app.put(d); }; } struct make_rule { using body_func = std::function< void(string_range, iterator_range) >; using depend_func = std::function< void(string_range, decltype(appender>()) &) >; make_rule() = delete; make_rule(string_range target): p_target(target) {} make_pattern &target() noexcept { return p_target; } make_pattern const &target() const noexcept { return p_target; } bool action() const noexcept { return p_action; } make_rule &action(bool act) noexcept { p_action = act; return *this; } make_rule &body(std::function act_f) noexcept { if (act_f) { p_body = [act_f = std::move(act_f)](auto, auto) { act_f(); }; } else { p_body = body_func{}; } return *this; } bool has_body() const noexcept { return !!p_body; } make_rule &body(body_func rule_f) noexcept { p_body = std::move(rule_f); return *this; } make_rule &cond(std::function cond_f) noexcept { p_cond = std::move(cond_f); return *this; } bool cond(string_range target) const { if (!p_cond) { return true; } return p_cond(target); } void depends( string_range tgt, std::function body ) const { auto app = appender>(); for (auto &f: p_deps) { app.clear(); f(tgt, app); for (auto &s: app.get()) { body(s); } } } template make_rule &depend(A &&...args) { (add_depend(std::forward(args)), ...); return *this; } make_rule &depend(std::initializer_list il) { add_depend(il); return *this; } void call(string_range target, iterator_range srcs) { p_body(target, srcs); } private: using rule_body = std::function< void(string_range, iterator_range) >; template void add_depend(R &&v) { if constexpr (std::is_constructible_v) { p_deps.push_back(make_depend_simple(v)); } else if constexpr(std::is_constructible_v) { p_deps.push_back(std::forward(v)); } else { R mr{v}; for (auto const &sv: mr) { add_depend(sv); } } } make_pattern p_target; std::vector p_deps{}; body_func p_body{}; std::function p_cond{}; bool p_action = false; }; struct OSTD_EXPORT make_task { make_task() {} virtual ~make_task(); virtual bool done() const = 0; virtual void resume() = 0; virtual std::shared_future add_task(std::future f) = 0; }; namespace detail { struct make_task_simple: make_task { make_task_simple() = delete; make_task_simple( string_range target, std::vector deps, make_rule &rl ): p_body( [target, deps = std::move(deps), &rl]() mutable { rl.call(target, iterator_range( deps.data(), deps.data() + deps.size() )); } ) {} bool done() const { return p_futures.empty(); } void resume() { if (p_body) { std::exchange(p_body, nullptr)(); } /* go over futures and erase those that are done */ for (auto it = p_futures.begin(); it != p_futures.end();) { auto fs = it->wait_for(std::chrono::seconds(0)); if (fs == std::future_status::ready) { /* maybe propagate exception */ auto f = std::move(*it); it = p_futures.erase(it); try { f.get(); } catch (...) { p_futures.clear(); throw; } continue; } ++it; } } std::shared_future add_task(std::future f) { auto sh = f.share(); p_futures.push_back(sh); return sh; } private: std::function p_body; std::list> p_futures{}; }; } inline make_task *make_task_simple( string_range target, std::vector deps, make_rule &rl ) { return new detail::make_task_simple{target, std::move(deps), rl}; } struct OSTD_EXPORT make { using task_factory = std::function< make_task *(string_range, std::vector, make_rule &) >; make( task_factory factory, int threads = std::thread::hardware_concurrency() ): p_factory(factory) { p_tpool.start(threads); } void exec(string_range target); std::shared_future push_task(std::function func); make_rule &rule(string_range tgt) { p_rules.emplace_back(tgt); return p_rules.back(); } unsigned int threads() const noexcept { return p_tpool.threads(); } private: struct rule_inst { std::vector deps; make_rule *rule; }; OSTD_LOCAL void wait_rest(std::queue> &tasks); template void wait_for(F func) { std::queue> tasks; p_waiting.push(&tasks); try { func(); } catch (...) { p_waiting.pop(); throw; } p_waiting.pop(); wait_rest(tasks); } OSTD_LOCAL void exec_rlist( string_range tname, std::vector const &rlist ); OSTD_LOCAL void exec_rule( string_range target, string_range from = nullptr ); OSTD_LOCAL void find_rules( string_range target, std::vector &rlist ); std::vector p_rules{}; std::unordered_map> p_cache{}; thread_pool p_tpool{}; std::mutex p_mtx{}; std::condition_variable p_cond{}; std::stack> *> p_waiting{}; task_factory p_factory{}; make_task *p_current = nullptr; bool p_avail = false; }; /** @} */ } /* namespace build */ } /* namespace ostd */ #endif /** @} */