look for root directory based on exe path, not cwd

simplified inis: look for mob.ini in root and cwd, add MOBINI and --ini
added inis command
options command now shows all the options after loading the inis
This commit is contained in:
isanae
2020-05-19 15:36:30 -04:00
parent 4d41d8b85e
commit 1f3dc93b5e
9 changed files with 306 additions and 124 deletions
+106 -46
View File
@@ -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<fs::path> 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<fs::path>& 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 <task name>...` 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<v.size(); ++i)
u8cout << (i + 1) << ") " << path_to_utf8(v[i]) << "\n";
return 0;
}
std::string inis_command::do_doc()
{
return "Shows which INIs are found.";
}
} // namespace
+22 -2
View File
@@ -17,6 +17,7 @@ public:
std::vector<std::string> options;
std::vector<std::string> 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<fs::path>& 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<int> code_;
std::vector<fs::path> 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
+125 -65
View File
@@ -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<std::string> conf::format_options()
std::vector<std::string> 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<std::string>& 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<max_tries; ++tries)
{
auto buffer = std::make_unique<wchar_t[]>(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<std::string> 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<fs::path> find_inis(const std::vector<fs::path>& inis_from_cl)
std::vector<fs::path> find_inis(
bool auto_detect, const std::vector<std::string>& from_cl, bool verbose)
{
const auto master = find_master_ini();
std::vector<fs::path> 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<fs::path>& inis_from_cl, bool auto_detection,
const std::vector<std::string>& opts)
const std::vector<fs::path>& inis, const std::vector<std::string>& opts)
{
std::vector<fs::path> inis;
if (auto_detection)
inis = find_inis(inis_from_cl);
else
inis = inis_from_cl;
MOB_ASSERT(!inis.empty());
bool add = true;
+5 -2
View File
@@ -103,9 +103,12 @@ struct paths
std::string master_ini_filename();
std::vector<fs::path> find_inis(
bool auto_detect, const std::vector<std::string>& from_cl,
bool verbose);
void init_options(
const std::vector<fs::path>& inis_from_cl, bool auto_detection,
const std::vector<std::string>& opts);
const std::vector<fs::path>& inis, const std::vector<std::string>& opts);
bool verify_options();
void log_options();
+20 -7
View File
@@ -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<std::string> this_env::get_opt(const std::string& name)
{
auto v = get_impl(name);
if (v)
return utf16_to_utf8(*v);
else
return {};
}
std::optional<std::wstring> 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<wchar_t[]>(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<DWORD>(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()
+3 -1
View File
@@ -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<std::string> get_opt(const std::string& k);
private:
static std::wstring get_impl(const std::string& k);
static std::optional<std::wstring> get_impl(const std::string& k);
};
} // namespace
+2 -1
View File
@@ -24,7 +24,8 @@ std::shared_ptr<command> handle_command_line(const std::vector<std::string>& arg
std::make_unique<list_command>(),
std::make_unique<release_command>(),
std::make_unique<git_command>(),
std::make_unique<cmake_command>()
std::make_unique<cmake_command>(),
std::make_unique<inis_command>()
};
+21
View File
@@ -322,6 +322,27 @@ std::string join(const std::vector<std::string>& v, const std::string& sep)
return s;
}
std::vector<std::string> split(const std::string& s, const std::string& seps)
{
std::vector<std::string> 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())
+2
View File
@@ -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<std::string>& v, const std::string& sep);
std::vector<std::string> 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=' ');