diff --git a/src/commands.cpp b/src/commands.cpp index 0bcf0f1..f4c7902 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -14,6 +14,7 @@ std::string version() return "mob 3.0"; } + void help(const clipp::group& g, const std::string& more) { #pragma warning(suppress: 4548) @@ -48,6 +49,7 @@ command::command(flags f) clipp::group command::common_options_group() { auto& o = common; + const auto master = master_ini_filename(); return (clipp::repeatable(clipp::option("-i", "--ini") @@ -78,7 +80,8 @@ clipp::group command::common_options_group() % "sets an option, such as 'versions/openssl=1.2'", (clipp::option("--no-default-inis") >> o.no_default_inis) - % "disables auto detection of ini files, only uses --ini"; + % "disables auto loading of ini files, only uses --ini; the first" + "--ini must be the master ini file"; } void command::force_exit_code(int code) @@ -111,14 +114,8 @@ clipp::group command::group() return do_group(); } -int command::run() +void command::convert_cl_to_conf() { - if (help_) - { - help(group(), do_doc()); - return 0; - } - auto& o = common; if (o.file_log_level == -1) @@ -146,25 +143,53 @@ int command::run() if (!o.prefix.empty()) o.options.push_back("paths/prefix=" + o.prefix); +} - do_pre_run(); +int command::gather_inis(bool verbose) +{ + auto& o = common; + + if (o.no_default_inis && o.inis.empty()) + { + u8cerr + << "--no-default-inis requires at least one --ini for the " + << "master ini file\n"; + + return 1; + } + + try + { + inis_ = find_inis(!o.no_default_inis, o.inis, verbose); + return 0; + } + catch(bailed&) + { + return 1; + } +} + +int command::prepare_options(bool verbose) +{ + convert_cl_to_conf(); + return gather_inis(verbose); +} + +int command::run() +{ + if (help_) + { + help(group(), do_doc()); + return 0; + } if (flags_ & requires_options) { - if (o.no_default_inis && o.inis.empty()) - { - u8cerr - << "--no-default-inis requires at least one --ini for the " - << "master ini file\n"; + const int r = prepare_options(false); + if (r != 0) + return r; - return 1; - } - - std::vector inis; - for (auto&& s : o.inis) - inis.push_back(s); - - init_options(inis, !o.no_default_inis, o.options); + init_options(inis_, common.options); log_options(); if (!verify_options()) @@ -179,6 +204,11 @@ int command::run() return r; } +const std::vector& command::inis() const +{ + return inis_; +} + clipp::group version_command::do_group() { @@ -204,18 +234,19 @@ int help_command::do_run() #pragma warning(suppress: 4548) auto doc = (command::common_options_group(), (clipp::value("command"))); - const auto mobini = master_ini_filename(); + const auto master = master_ini_filename(); help(doc, "Commands:\n" " help shows this message\n" " version shows the version\n" " list lists available tasks\n" - " options lists available options and default values\n" + " options lists all options and their values from the inis\n" " build builds tasks\n" " release creates a release or a devbuild\n" " git manages the git repos\n" " cmake runs cmake in a directory\n" + " inis lists the INIs used by mob (debug)\n" "\n\n" "Invoking `mob -d some/prefix build` builds everything. Do \n" "`mob build ...` to build specific tasks. See\n" @@ -223,25 +254,24 @@ int help_command::do_run() "\n" "INI files\n" "\n" - "By default, mob will look for a master INI `" + mobini + "` in the \n" - "current directory and up to three of its parents (so it can also be\n" - "found from the build directory). Once mob found the master INI, it\n" - "will look for any other .ini file in the same directory.\n" - "\n" - "These additional INI files will be loaded after the master, in\n" - "lexicographical order, overriding anything the previous INI file\n" - "may have set.\n" - "\n" - "Additional INI files may be specified with --ini. They will be\n" - "loaded in order after the ones that were auto detected. If the same\n" - "INI file is found in the directory and on the command line, its\n" - "position in the load order will be moved to that of the command\n" - "line."); + "By default, mob will look for a master INI `" + master + "` in the \n" + "root directory (typically where mob.exe resides). Once mob has\n" + "found the master INI, it will look for the same filename in the\n" + "current directory, if different from the root. If found, both will\n" + "be loaded, but the one in the current directory will override the\n" + "the other. Additional INIs can be specified with --ini, those will\n" + "be loaded after the two mentioned above. Use --no-default-inis to\n" + "only disable auto detection and only use --ini."); return 0; } +options_command::options_command() + : command(requires_options) +{ +} + clipp::group options_command::do_group() { return clipp::group( @@ -260,10 +290,7 @@ int options_command::do_run() std::string options_command::do_doc() { - return - "Lists all available options in the form of `section/key = default`.\n" - "They can be changed in the INI file by setting `key = value` within\n" - "`[section]` or with `-s section/key=value`."; + return "Lists the final value of all options found by loading the INIs."; } @@ -304,16 +331,18 @@ clipp::group build_command::do_group() "projects"; } -void build_command::do_pre_run() +void build_command::convert_cl_to_conf() { + command::convert_cl_to_conf(); + if (redownload_ || clean_) - common.options.push_back("options/redownload=true"); + common.options.push_back("global/redownload=true"); if (reextract_ || clean_) - common.options.push_back("options/reextract=true"); + common.options.push_back("global/reextract=true"); if (rebuild_ || clean_) - common.options.push_back("options/rebuild=true"); + common.options.push_back("global/rebuild=true"); if (nopull_) common.options.push_back("options/no_pull=true"); @@ -971,4 +1000,35 @@ std::string cmake_command::do_doc() "as the one used for modorganizer projects."; } + +clipp::group inis_command::do_group() +{ + return clipp::group( + clipp::command("inis").set(picked_), + + (clipp::option("-h", "--help") >> help_) + % ("shows this message") + ); +} + +int inis_command::do_run() +{ + const auto r = prepare_options(true); + if (r != 0) + return r; + + u8cout << "\nhigher number overrides lower\n"; + + const auto v = inis(); + for (std::size_t i=0; i options; std::vector inis; bool no_default_inis = false; + bool dump_inis = false; std::string prefix; }; @@ -34,6 +35,8 @@ public: clipp::group group(); int run(); + const std::vector& inis() const; + protected: enum flags { @@ -46,14 +49,19 @@ protected: command(flags f=noflags); + virtual void convert_cl_to_conf(); + int prepare_options(bool verbose); + virtual clipp::group do_group() = 0; - virtual void do_pre_run() {}; virtual int do_run() = 0; virtual std::string do_doc() { return {}; } private: flags flags_; std::optional code_; + std::vector inis_; + + int gather_inis(bool verbose); }; @@ -80,6 +88,7 @@ protected: class options_command : public command { public: + options_command(); protected: clipp::group do_group() override; @@ -94,8 +103,8 @@ public: build_command(); protected: + void convert_cl_to_conf() override; clipp::group do_group() override; - void do_pre_run() override; int do_run() override; private: @@ -215,4 +224,15 @@ private: std::string path_; }; + +class inis_command : public command +{ +public: + +protected: + clipp::group do_group() override; + int do_run() override; + std::string do_doc() override; +}; + } // namespace diff --git a/src/conf.cpp b/src/conf.cpp index 8aabafe..35bf1e0 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -9,15 +9,15 @@ namespace mob { +conf::task_map conf::map_; +int conf::output_log_level_ = 3; +int conf::file_log_level_ = 5; + std::string master_ini_filename() { return "mob.ini"; } -conf::task_map conf::map_; -int conf::output_log_level_ = 3; -int conf::file_log_level_ = 5; - std::string conf::get_global(const std::string& section, const std::string& key) { @@ -232,6 +232,18 @@ std::vector conf::format_options() std::vector lines; + lines.push_back( + pad_right("task", longest_task) + " " + + pad_right("section", longest_section) + " " + + pad_right("key",longest_key) + " " + + "value"); + + lines.push_back( + pad_right("-", longest_task, '-') + " " + + pad_right("-", longest_section, '-') + " " + + pad_right("-",longest_key, '-') + " " + + "-----"); + for (auto&& [t, ss] : map_) { for (auto&& [s, kv] : ss) @@ -297,25 +309,51 @@ bool try_parts(fs::path& check, const std::vector& parts) return false; } -fs::path find_root_impl() +fs::path mob_exe_path() { - gcx().trace(context::conf, "looking for root directory"); + // double the buffer size 10 times + const int max_tries = 10; - fs::path p = fs::current_path(); + DWORD buffer_size = MAX_PATH; - if (try_parts(p, {"..", "..", "..", "third-party"})) - return p; + for (int tries=0; tries(buffer_size + 1); + DWORD n = GetModuleFileNameW(0, buffer.get(), buffer_size); - gcx().bail_out(context::conf, "root directory not found"); + if (n == 0) { + const auto e = GetLastError(); + gcx().bail_out(context::conf, + "can't get module filename, {}", error_message(e)); + } + else if (n >= buffer_size) { + // buffer is too small, try again + buffer_size *= 2; + } else { + // if GetModuleFileName() works, `n` does not include the null + // terminator + const std::wstring s(buffer.get(), n); + return fs::canonical(s); + } + } + + gcx().bail_out(context::conf, "can't get module filename"); } fs::path find_root() { - const auto p = find_root_impl().parent_path(); + gcx().trace(context::conf, "looking for root directory"); - gcx().trace(context::conf, "found root directory at {}", p); + fs::path p = mob_exe_path().parent_path(); - return p; + if (try_parts(p, {"..", "..", "..", "third-party"})) + { + p = fs::canonical(p.parent_path()); + gcx().trace(context::conf, "found root directory at {}", p); + return p; + } + + gcx().bail_out(context::conf, "root directory not found"); } fs::path find_in_root(const fs::path& file) @@ -593,17 +631,6 @@ void ini_error(const fs::path& ini, std::size_t line, const std::string& what) ini.filename(), (line + 1), what); } -fs::path find_master_ini() -{ - auto p = fs::current_path(); - - if (try_parts(p, {"..", "..", "..", master_ini_filename()})) - return fs::canonical(p); - - gcx().bail_out(context::conf, - "can't find master ini {}", master_ini_filename()); -} - std::vector read_ini(const fs::path& ini) { std::ifstream in(ini); @@ -783,67 +810,100 @@ void set_special_options() conf::set_file_log_level(conf::get_global("global", "file_log_level")); } -std::vector find_inis(const std::vector& inis_from_cl) +std::vector find_inis( + bool auto_detect, const std::vector& from_cl, bool verbose) { - const auto master = find_master_ini(); - std::vector v; - for (auto&& e : fs::directory_iterator(master.parent_path())) + auto add_or_move_up = [&](fs::path p) { - const auto p = e.path(); - - if (path_to_utf8(p.extension()) != ".ini") - continue; - - if (p.filename() == master.filename()) - continue; - - v.push_back(p); - } - - std::sort(v.begin(), v.end()); - v.insert(v.begin(), master); - - for (auto&& p : inis_from_cl) - { - if (!fs::exists(p)) - { - u8cerr << "ini " << p << " not found\n"; - throw bailed(); - } - - bool found = false; - for (auto itor=v.begin(); itor!=v.end(); ++itor) { if (fs::equivalent(p, *itor)) { - found = true; v.erase(itor); v.push_back(p); - break; + return; } } - if (!found) - v.push_back(p); + v.push_back(p); + }; + + + // auto detect from exe directory and cwd + if (auto_detect) + { + if (verbose) + u8cout << "root is " << path_to_utf8(find_root()) << "\n"; + + const auto master = find_in_root(master_ini_filename()); + + if (verbose) + u8cout << "found master " << master_ini_filename() << "\n"; + + v.push_back(master); + + const auto in_cwd = fs::current_path() / master_ini_filename(); + if (fs::exists(in_cwd) && !fs::equivalent(in_cwd, master)) + { + if (verbose) + u8cout << "also found in cwd " << path_to_utf8(in_cwd) << "\n"; + + v.push_back(fs::canonical(in_cwd)); + } + } + + // MOBINI environment variable + if (auto e=this_env::get_opt("MOBINI")) + { + if (verbose) + u8cout << "found env MOBINI: " << *e << "\n"; + + for (auto&& i : split(*e, ";")) + { + if (verbose) + u8cout << "checking '" << i << "'\n"; + + auto p = fs::path(i); + if (!fs::exists(p)) + { + u8cerr << "ini from env MOBINI " << i << " not found\n"; + throw bailed(); + } + + p = fs::canonical(p); + + if (verbose) + u8cout << "ini from env: " << path_to_utf8(p) << "\n"; + + add_or_move_up(p); + } + } + + for (auto&& i : from_cl) + { + auto p = fs::path(i); + if (!fs::exists(p)) + { + u8cerr << "ini " << i << " not found\n"; + throw bailed(); + } + + p = fs::canonical(p); + + if (verbose) + u8cout << "ini from command line: " << path_to_utf8(p) << "\n"; + + add_or_move_up(p); } return v; } void init_options( - const std::vector& inis_from_cl, bool auto_detection, - const std::vector& opts) + const std::vector& inis, const std::vector& opts) { - std::vector inis; - - if (auto_detection) - inis = find_inis(inis_from_cl); - else - inis = inis_from_cl; - MOB_ASSERT(!inis.empty()); bool add = true; diff --git a/src/conf.h b/src/conf.h index fa3307e..eefbd0b 100644 --- a/src/conf.h +++ b/src/conf.h @@ -103,9 +103,12 @@ struct paths std::string master_ini_filename(); +std::vector find_inis( + bool auto_detect, const std::vector& from_cl, + bool verbose); + void init_options( - const std::vector& inis_from_cl, bool auto_detection, - const std::vector& opts); + const std::vector& inis, const std::vector& opts); bool verify_options(); void log_options(); diff --git a/src/env.cpp b/src/env.cpp index a683b27..87763a8 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -224,14 +224,14 @@ void this_env::set(const std::string& k, const std::string& v, env::flags f) case env::append: { - const std::wstring current = get_impl(k); + const std::wstring current = get_impl(k).value_or(L""); ::SetEnvironmentVariableW(wk.c_str(), (current + wv).c_str()); break; } case env::prepend: { - const std::wstring current = get_impl(k); + const std::wstring current = get_impl(k).value_or(L""); ::SetEnvironmentVariableW(wk.c_str(), (wv + current).c_str()); break; } @@ -246,10 +246,23 @@ void this_env::prepend_to_path(const fs::path& p) std::string this_env::get(const std::string& name) { - return utf16_to_utf8(get_impl(name)); + auto v = get_impl(name); + if (!v) + bail_out("environment variable {} doesn't exist", name); + + return utf16_to_utf8(*v); } -std::wstring this_env::get_impl(const std::string& k) +std::optional this_env::get_opt(const std::string& name) +{ + auto v = get_impl(name); + if (v) + return utf16_to_utf8(*v); + else + return {}; +} + +std::optional this_env::get_impl(const std::string& k) { const std::wstring wk = utf8_to_utf16(k); @@ -257,7 +270,7 @@ std::wstring this_env::get_impl(const std::string& k) wk.c_str(), nullptr, 0); if (buffer_size == 0) - bail_out("environment variable {} doesn't exist", k); + return {}; auto buffer = std::make_unique(buffer_size + 1); std::fill(buffer.get(), buffer.get() + buffer_size + 1, 0); @@ -266,11 +279,11 @@ std::wstring this_env::get_impl(const std::string& k) wk.c_str(), buffer.get(), static_cast(buffer_size)); if (written == 0) - bail_out("environment variable {} doesn't exist", k); + return {}; MOB_ASSERT((written + 1) == buffer_size); - return {buffer.get(), buffer.get() + written}; + return std::wstring(buffer.get(), buffer.get() + written); } env this_env::get() diff --git a/src/env.h b/src/env.h index 12c6cc4..ed6abe3 100644 --- a/src/env.h +++ b/src/env.h @@ -50,10 +50,12 @@ struct this_env static void prepend_to_path(const fs::path& p); static env get(); + static std::string get(const std::string& k); + static std::optional get_opt(const std::string& k); private: - static std::wstring get_impl(const std::string& k); + static std::optional get_impl(const std::string& k); }; } // namespace diff --git a/src/main.cpp b/src/main.cpp index 20bb145..2469739 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,8 @@ std::shared_ptr handle_command_line(const std::vector& arg std::make_unique(), std::make_unique(), std::make_unique(), - std::make_unique() + std::make_unique(), + std::make_unique() }; diff --git a/src/utility.cpp b/src/utility.cpp index 0964bcb..eed97e2 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -322,6 +322,27 @@ std::string join(const std::vector& v, const std::string& sep) return s; } +std::vector split(const std::string& s, const std::string& seps) +{ + std::vector v; + + std::size_t start = 0; + + while (start < s.size()) + { + auto p = s.find_first_of(seps, start); + if (p == std::string::npos) + p = s.size(); + + if (p - start > 0) + v.push_back(s.substr(start, p - start)); + + start = p + 1; + } + + return v; +} + void trim(std::string& s, const std::string& what) { while (!s.empty()) diff --git a/src/utility.h b/src/utility.h index 38b10fb..05f5ef5 100644 --- a/src/utility.h +++ b/src/utility.h @@ -232,6 +232,8 @@ std::string replace_all( std::string s, const std::string& from, const std::string& to); std::string join(const std::vector& v, const std::string& sep); +std::vector split(const std::string& s, const std::string& sep); + std::string pad_right(std::string s, std::size_t n, char c=' '); std::string pad_left(std::string s, std::size_t n, char c=' ');