From 977d07a3ae47ef204665d1eda2d642e5064724f3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 24 Jun 2019 12:01:19 +0200 Subject: Split build system into library and driver --- libbuild2/utility.hxx | 671 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 671 insertions(+) create mode 100644 libbuild2/utility.hxx (limited to 'libbuild2/utility.hxx') diff --git a/libbuild2/utility.hxx b/libbuild2/utility.hxx new file mode 100644 index 0000000..af72c58 --- /dev/null +++ b/libbuild2/utility.hxx @@ -0,0 +1,671 @@ +// file : libbuild2/utility.hxx -*- C++ -*- +// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef LIBBUILD2_UTILITY_HXX +#define LIBBUILD2_UTILITY_HXX + +#include // make_tuple() +#include // make_shared() +#include // to_string() +#include // move(), forward(), declval(), make_pair(), swap() +#include // assert() +#include // make_move_iterator() +#include // * +#include // ref(), cref() + +#include + +#include // combine_hash(), reverse_iterate(), etc + +#include + +#include + +// "Fake" version values used during bootstrap. +// +#ifdef BUILD2_BOOTSTRAP +# define LIBBUILD2_VERSION 9999999999999990000ULL +# define LIBBUILD2_VERSION_STR "99999.99999.99999" +# define LIBBUILD2_VERSION_ID "99999.99999.99999" +# define LIBBUTL_VERSION_STR "99999.99999.99999" +# define LIBBUTL_VERSION_ID "99999.99999.99999" +#else +# include +#endif + +#include + +namespace build2 +{ + using std::move; + using std::swap; + 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::trim; + using butl::next_word; + + using butl::make_guard; + using butl::make_exception_guard; + + using butl::getenv; + using butl::setenv; + using butl::unsetenv; + + using butl::throw_generic_error; + using butl::throw_system_error; + + using butl::eof; + + // Diagnostics state (verbosity level, etc; see diagnostics.hxx). + // + // Note on naming of values (here and in the global state below) that come + // from the command line options: if a value is not meant to be used + // directly, then it has the _option suffix and a function or another + // variable as its public interface. + + // Initialize the diagnostics state. Should be called once early in main(). + // Default values are for unit tests. + // + LIBBUILD2_SYMEXPORT void + init_diag (uint16_t verbosity, + optional progress = nullopt, + bool no_lines = false, + bool no_columns = false, + bool stderr_term = false); + + LIBBUILD2_SYMEXPORT extern uint16_t verb; + const uint16_t verb_never = 7; + + // --[no-]progress + // + LIBBUILD2_SYMEXPORT extern optional diag_progress_option; + + LIBBUILD2_SYMEXPORT extern bool diag_no_line; // --no-line + LIBBUILD2_SYMEXPORT extern bool diag_no_column; // --no-column + + LIBBUILD2_SYMEXPORT extern bool stderr_term; // True if stderr is a terminal. + + // Global state (verbosity, home/work directories, etc). + + // Initialize the global state. Should be called once early in main(). + // Default values are for unit tests. + // + LIBBUILD2_SYMEXPORT void + init (const char* argv0, + bool keep_going = false, + bool dry_run = false, + optional mtime_check = nullopt, + optional config_sub = nullopt, + optional config_guess = nullopt); + + // Build system driver process path (argv0.initial is argv[0]). + // + LIBBUILD2_SYMEXPORT extern process_path argv0; + + // Build system driver version and check. + // + LIBBUILD2_SYMEXPORT extern const standard_version build_version; + + LIBBUILD2_SYMEXPORT extern bool dry_run_option; // --dry-run + + // --[no-]mtime-check + // + LIBBUILD2_SYMEXPORT extern optional mtime_check_option; + + LIBBUILD2_SYMEXPORT extern optional config_sub; // --config-sub + LIBBUILD2_SYMEXPORT extern optional config_guess; // --config-guess + + class location; + + LIBBUILD2_SYMEXPORT void + check_build_version (const standard_version_constraint&, const location&); + + // Work/home directories (must be initialized in main()) and relative path + // calculation. + // + LIBBUILD2_SYMEXPORT extern dir_path work; + LIBBUILD2_SYMEXPORT 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. + // + LIBBUILD2_SYMEXPORT 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&); + + class path_target; + + LIBBUILD2_SYMEXPORT path + relative (const path_target&); + + // 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. + // + LIBBUILD2_SYMEXPORT string + diag_relative (const path&, bool current = true); + + // Basic process utilities. + // + // The run*() functions with process_path assume that you are printing + // the process command line yourself. + + // Search for a process executable. Issue diagnostics and throw failed in + // case of an error. + // + LIBBUILD2_SYMEXPORT process_path + run_search (const char*& args0, + bool path_only, + const location& = location ()); + + inline process_path + run_search (const char*& args0, const location& l = location ()) + { + return run_search (args0, false, l); + } + + LIBBUILD2_SYMEXPORT process_path + run_search (const path&, + bool init = false, + const dir_path& fallback = dir_path (), + bool path_only = false, + const location& = location ()); + + LIBBUILD2_SYMEXPORT process_path + try_run_search (const path&, + bool init = false, + const dir_path& fallback = dir_path (), + bool path_only = false); + + // Wait for process termination. Issue diagnostics and throw failed in case + // of abnormal termination. If the process has terminated normally but with + // a non-zero exit status, then, if error is true, assume the diagnostics + // has already been issued and throw failed as well. Otherwise (error is + // false), return false. The last argument is used in cooperation with + // run_start() in case STDERR is redirected to STDOUT. + // + LIBBUILD2_SYMEXPORT bool + run_finish (const char* args[], + process&, + bool error = true, + const string& = string (), + const location& = location ()); + + inline void + run_finish (cstrings& args, process& pr, const location& l = location ()) + { + run_finish (args.data (), pr, true, string (), l); + } + + // Start a process with the specified arguments. If in is -1, then redirect + // STDIN to a pipe (can also be -2 to redirect to /dev/null or equivalent). + // If out is -1, 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. + // + LIBBUILD2_SYMEXPORT process + run_start (uint16_t verbosity, + const process_env&, // Implicit-constructible from process_path. + const char* args[], + int in, + int out, + bool error = true, + const dir_path& cwd = dir_path (), + const location& = location ()); + + inline process + run_start (const process_env& pe, // Implicit-constructible from process_path. + const char* args[], + int in, + int out, + bool error = true, + const dir_path& cwd = dir_path (), + const location& l = location ()) + { + return run_start (verb_never, pe, args, in, out, error, cwd, l); + } + + inline void + run (const process_path& p, + const char* args[], + const dir_path& cwd = dir_path ()) + { + process pr (run_start (p, args, 0 /* stdin */, 1 /* stdout */, true, cwd)); + run_finish (args, pr); + } + + inline void + run (const process_path& p, + cstrings& args, + const dir_path& cwd = dir_path ()) + { + run (p, args.data (), cwd); + } + + // As above, but search for the process (including updating args[0]) and + // print the process commands line at the specified verbosity level. + // + inline process + run_start (uint16_t verbosity, + const char* args[], + int in, + int out, + bool error = true, + const dir_path& cwd = dir_path (), + const location& l = location ()) + { + process_path pp (run_search (args[0], l)); + return run_start (verbosity, pp, args, in, out, error, cwd, l); + } + + inline process + run_start (uint16_t verbosity, + cstrings& args, + int in, + int out, + bool error = true, + const dir_path& cwd = dir_path (), + const location& l = location ()) + { + return run_start (verbosity, args.data (), in, out, error, cwd, l); + } + + inline void + run (uint16_t verbosity, + const char* args[], + const dir_path& cwd = dir_path ()) + { + process pr (run_start (verbosity, + args, + 0 /* stdin */, + 1 /* stdout */, + true, + cwd)); + run_finish (args, pr); + } + + inline void + run (uint16_t verbosity, + cstrings& args, + const dir_path& cwd = dir_path ()) + { + run (verbosity, args.data (), cwd); + } + + // 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). The function signature should be: + // + // T (string& line, bool last) + // + // If ignore_exit is true, then the program's exit 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 trimmed line + // (including those that come after the callback returns non-empty object). + // + template + T + run (uint16_t verbosity, + const process_env&, // Implicit-constructible from process_path. + const char* args[], + F&&, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr); + + template + inline T + run (const process_env& pe, // Implicit-constructible from process_path. + const char* args[], + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + return run ( + verb_never, pe, args, forward (f), error, ignore_exit, checksum); + } + + template + inline T + run (uint16_t verbosity, + const char* args[], + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + process_path pp (run_search (args[0])); + return run ( + verbosity, pp, args, forward (f), error, ignore_exit, checksum); + } + + // run + // + template + inline T + run (uint16_t verbosity, + 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 ( + verbosity, args, forward (f), error, ignore_exit, checksum); + } + + template + inline T + run (uint16_t verbosity, + const process_env& pe, // Implicit-constructible from process_path. + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {pe.path->recall_string (), nullptr}; + return run ( + verbosity, pe, args, forward (f), error, ignore_exit, checksum); + } + + // run + // + template + inline T + run (uint16_t verbosity, + 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 ( + verbosity, args, forward (f), error, ignore_exit, checksum); + } + + template + inline T + run (uint16_t verbosity, + const process_env& pe, // Implicit-constructible from process_path. + const char* arg, + F&& f, + bool error = true, + bool ignore_exit = false, + sha256* checksum = nullptr) + { + const char* args[] = {pe.path->recall_string (), arg, nullptr}; + return run ( + verbosity, pe, args, forward (f), error, ignore_exit, checksum); + } + + // Empty/nullopt string, path, and project name. + // + LIBBUILD2_SYMEXPORT extern const string empty_string; + LIBBUILD2_SYMEXPORT extern const path empty_path; + LIBBUILD2_SYMEXPORT extern const dir_path empty_dir_path; + LIBBUILD2_SYMEXPORT extern const project_name empty_project_name; + + LIBBUILD2_SYMEXPORT extern const optional nullopt_string; + LIBBUILD2_SYMEXPORT extern const optional nullopt_path; + LIBBUILD2_SYMEXPORT extern const optional nullopt_dir_path; + LIBBUILD2_SYMEXPORT extern const optional nullopt_project_name; + + // Hash a path potentially without the specific directory prefix. + // + // If prefix is not empty and is a super-path of the path to hash, then only + // hash the suffix. Note that both paths are assumed to be normalized. + // + // This functionality is normally used to strip out_root from target paths + // being hashed in order to avoid updates in case out_root was moved. Note + // that this should only be done if the result of the update does not + // include the out_root path in any form (as could be the case, for example, + // for debug information, __FILE__ macro expansion, rpath, etc). + // + void + hash_path (sha256&, const path&, const dir_path& prefix = 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. + // + // If excl is not NULL, then filter this option out (note: case sensitive). + // + struct variable; + + template + void + append_options (cstrings&, T&, const variable&, const char* excl = nullptr); + + template + void + append_options (cstrings&, T&, const char*, const char* excl = nullptr); + + template + void + append_options (strings&, T&, const variable&, const char* excl = nullptr); + + template + void + append_options (strings&, T&, const char*, const char* excl = nullptr); + + 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; + + LIBBUILD2_SYMEXPORT void + append_options (cstrings&, const lookup&, const char* excl = nullptr); + + LIBBUILD2_SYMEXPORT void + append_options (strings&, const lookup&, const char* excl = nullptr); + + LIBBUILD2_SYMEXPORT void + hash_options (sha256&, const lookup&); + + void + append_options (cstrings&, const strings&, const char* excl = nullptr); + + void + append_options (strings&, const strings&, const char* excl = nullptr); + + void + hash_options (sha256&, const strings&); + + LIBBUILD2_SYMEXPORT void + append_options (cstrings&, + const strings&, size_t, + const char* excl = nullptr); + + LIBBUILD2_SYMEXPORT void + append_options (strings&, + const strings&, size_t, + const char* excl = nullptr); + + LIBBUILD2_SYMEXPORT void + hash_options (sha256&, const strings&, size_t); + + // As above but append/hash option values for the specified option (e.g., + // -I, -L). + // + template + void + append_option_values (cstrings&, + const char* opt, + I begin, I end, + F&& get = [] (const string& s) {return s.c_str ();}); + + template + void + hash_option_values (sha256&, + const char* opt, + I begin, I end, + F&& get = [] (const string& s) {return s;}); + + // 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); + + LIBBUILD2_SYMEXPORT bool + find_option (const char* option, const lookup&, bool ignore_case = false); + + LIBBUILD2_SYMEXPORT bool + find_option (const char* option, const strings&, bool ignore_case = false); + + LIBBUILD2_SYMEXPORT bool + find_option (const char* option, const cstrings&, bool ignore_case = false); + + // As above but look for several options returning true if any is present. + // + template + bool + find_options (initializer_list, + T&, + const variable&, + bool = false); + + template + bool + find_options (initializer_list, T&, const char*, bool = false); + + LIBBUILD2_SYMEXPORT bool + find_options (initializer_list, const lookup&, bool = false); + + LIBBUILD2_SYMEXPORT bool + find_options (initializer_list, const strings&, bool = false); + + LIBBUILD2_SYMEXPORT bool + find_options (initializer_list, const cstrings&, bool = false); + + // As above but look for an option that has the specified prefix. Return the + // pointer to option or NULL if not found (thus can be used as bool). + // Search backward (which is normall consistent with how options override + // each other). + // + template + const string* + find_option_prefix (const char* prefix, T&, const variable&, bool = false); + + template + const string* + find_option_prefix (const char* prefix, T&, const char*, bool = false); + + LIBBUILD2_SYMEXPORT const string* + find_option_prefix (const char* prefix, const lookup&, bool = false); + + LIBBUILD2_SYMEXPORT const string* + find_option_prefix (const char* prefix, const strings&, bool = false); + + LIBBUILD2_SYMEXPORT const char* + find_option_prefix (const char* prefix, const cstrings&, bool = false); + + // As above but look for several option prefixes. + // + template + const string* + find_option_prefixes (initializer_list, + T&, + const variable&, + bool = false); + + template + const string* + find_option_prefixes (initializer_list, + T&, + const char*, + bool = false); + + LIBBUILD2_SYMEXPORT const string* + find_option_prefixes (initializer_list, + const lookup&, bool = false); + + LIBBUILD2_SYMEXPORT const string* + find_option_prefixes (initializer_list, + const strings&, + bool = false); + + LIBBUILD2_SYMEXPORT const char* + find_option_prefixes (initializer_list, + const cstrings&, + bool = false); + + // Apply the specified substitution (stem) to a '*'-pattern. If pattern is + // NULL or empty, then return the stem itself. Assume the pattern is valid, + // i.e., contains a single '*' character. + // + LIBBUILD2_SYMEXPORT string + apply_pattern (const char* stem, const string* pattern); +} + +#include +#include + +#endif // LIBBUILD2_UTILITY_HXX -- cgit v1.1