From e9acdb3c0b3812ec5f7730739488bfe53aec2249 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 21 Nov 2020 03:50:53 -0500 Subject: [PATCH] paths refactoring, comments --- src/core/conf.cpp | 3 +- src/core/paths.cpp | 515 +++++++++++++++++++++++---------------------- src/core/paths.h | 61 ++++-- 3 files changed, 317 insertions(+), 262 deletions(-) diff --git a/src/core/conf.cpp b/src/core/conf.cpp index 80a8093..5536087 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -452,8 +452,7 @@ void init_options( set_path_if_empty("licenses", find_in_root("licenses")); set_path_if_empty("qt_bin", qt::installation_path() / "bin"); - find_vcvars(); - validate_qt(); + details::set_string("tools", "vcvars", path_to_utf8(find_vcvars())); this_env::append_to_path(conf().path().get("qt_bin")); diff --git a/src/core/paths.cpp b/src/core/paths.cpp index 729d86c..569fc0d 100644 --- a/src/core/paths.cpp +++ b/src/core/paths.cpp @@ -6,6 +6,24 @@ namespace mob { +// returns a path to the given known folder, empty on error +// +fs::path get_known_folder(const GUID& id) +{ + wchar_t* buffer = nullptr; + const auto r = ::SHGetKnownFolderPath(id, 0, 0, &buffer); + + if (r != S_OK) + return {}; + + fs::path p = buffer; + ::CoTaskMemFree(buffer); + + return p; +} + +// searches PATH for the given executable, returns empty if not found +// fs::path find_in_path(std::string_view exe) { const std::wstring wexe = utf8_to_utf16(exe); @@ -13,51 +31,28 @@ fs::path find_in_path(std::string_view exe) const std::size_t size = MAX_PATH; wchar_t buffer[size + 1] = {}; - if (SearchPathW(nullptr, wexe.c_str(), nullptr, size, buffer, nullptr)) - return buffer; - else - return {}; -} - -fs::path find_iscc() -{ - const auto tasks = find_tasks("installer"); - MOB_ASSERT(tasks.size() == 1); - - if (!tasks[0]->enabled()) + if (SearchPathW(nullptr, wexe.c_str(), nullptr, size, buffer, nullptr) == 0) return {}; - auto iscc = conf().tool().get("iscc"); - if (iscc.is_absolute()) - { - if (!fs::exists(iscc)) - { - gcx().bail_out(context::conf, - "{} doesn't exist (from ini, absolute path)", iscc); - } - - return iscc; - } - - fs::path p = find_in_path(path_to_utf8(iscc)); - if (fs::exists(p)) - return fs::canonical(fs::absolute(p)); - - for (int v : {5, 6, 7, 8}) - { - const fs::path inno = fmt::format("inno setup {}", v); - - for (fs::path pf : {conf().path().pf_x86(), conf().path().pf_x64()}) - { - p = pf / inno / iscc; - if (fs::exists(p)) - return fs::canonical(fs::absolute(p)); - } - } - - gcx().bail_out(context::conf, "can't find {} anywhere", iscc); + return buffer; } +// checks if a path exists that starts with `check` and ends with as many parts +// as possible +// +// for example: +// +// try_parts("c:/", {"1", "2", "3"}) +// +// will try in order: +// +// c:/1/2/3 +// c:/2/3 +// c:/3 +// +// if none of the paths exist, returns false; if one of the paths exists, +// `check` is set to it and returns true +// bool try_parts(fs::path& check, const std::vector& parts) { for (std::size_t i=0; i& parts) return false; } -fs::path mob_exe_path() -{ - // double the buffer size 10 times - const int max_tries = 10; - - DWORD buffer_size = MAX_PATH; - - for (int tries=0; tries(buffer_size + 1); - DWORD n = GetModuleFileNameW(0, buffer.get(), buffer_size); - - 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(bool verbose) -{ - gcx().trace(context::conf, "looking for root directory"); - - fs::path mob_exe_dir = mob_exe_path().parent_path(); - - auto third_party = mob_exe_dir / "third-party"; - - if (!fs::exists(third_party)) - { - if (verbose) - u8cout << "no third-party here\n"; - - auto p = mob_exe_dir; - - if (p.filename().u8string() == u8"x64") - { - p = p.parent_path(); - if (p.filename().u8string() == u8"Debug" || - p.filename().u8string() == u8"Release") - { - if (verbose) - u8cout << "this is mob's build directory, looking up\n"; - - // mob_exe_dir is in the build directory - third_party = mob_exe_dir / ".." / ".." / ".." / "third-party"; - } - } - } - - if (!fs::exists(third_party)) - gcx().bail_out(context::conf, "root directory not found"); - - const auto p = fs::canonical(third_party.parent_path()); - gcx().trace(context::conf, "found root directory at {}", p); - return p; -} - -fs::path find_in_root(const fs::path& file) -{ - static fs::path root = find_root(); - - fs::path p = root / file; - if (!fs::exists(p)) - gcx().bail_out(context::conf, "{} not found", p); - - gcx().trace(context::conf, "found {}", p); - return p; -} - - -fs::path find_third_party_directory() -{ - static fs::path path = find_in_root("third-party"); - return path; -} - +// looks for `qmake.exe` in the given path, tries a variety of subdirectories +// bool find_qmake(fs::path& check) { // try Qt/Qt5.14.2/msvc*/bin/qmake.exe @@ -194,81 +103,102 @@ bool find_qmake(fs::path& check) return false; } -bool try_qt_location(fs::path& check) + +fs::path mob_exe_path() { - if (!find_qmake(check)) - return false; + const int max_tries = 3; - check = fs::absolute(check.parent_path() / ".."); - return true; -} + DWORD buffer_size = MAX_PATH; -fs::path find_qt() -{ - fs::path p = conf().path().qt_install(); - - if (!p.empty()) + for (int tries=0; tries(buffer_size + 1); + DWORD n = GetModuleFileNameW(0, buffer.get(), buffer_size); - if (!try_qt_location(p)) - gcx().bail_out(context::conf, "no qt install in {}", p); + if (n == 0) + { + const auto e = GetLastError(); - return p; + 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"); +} - std::vector locations = +fs::path find_root(bool verbose) +{ + gcx().trace(context::conf, "looking for root directory"); + + fs::path mob_exe_dir = mob_exe_path().parent_path(); + + auto third_party = mob_exe_dir / "third-party"; + + if (!fs::exists(third_party)) { - conf().path().pf_x64(), - "C:\\", - "D:\\" - }; + // doesn't exist, maybe this is the build directory - // look for qmake, which is in %qt%/version/msvc.../bin - fs::path qmake = find_in_path("qmake.exe"); - if (!qmake.empty()) - locations.insert(locations.begin(), qmake.parent_path() / "../../"); + if (verbose) + u8cout << path_to_utf8(third_party) << " doesn't exist\n"; - // look for qtcreator.exe, which is in %qt%/Tools/QtCreator/bin - fs::path qtcreator = find_in_path("qtcreator.exe"); - if (!qtcreator.empty()) - locations.insert(locations.begin(), qtcreator.parent_path() / "../../../"); + auto p = mob_exe_dir; - for (fs::path loc : locations) - { - loc = fs::absolute(loc); - if (try_qt_location(loc)) - return loc; + if (p.filename().u8string() == u8"x64") + { + p = p.parent_path(); + + if (p.filename().u8string() == u8"Debug" || + p.filename().u8string() == u8"Release") + { + if (verbose) + u8cout << "this is mob's build directory, looking up\n"; + + // mob_exe_dir is in the build directory + third_party = mob_exe_dir / ".." / ".." / ".." / "third-party"; + } + } } - gcx().bail_out(context::conf, "can't find qt install"); -} + if (!fs::exists(third_party)) + gcx().bail_out(context::conf, "root directory not found"); -void validate_qt() -{ - fs::path p = qt::installation_path(); - - if (!try_qt_location(p)) - gcx().bail_out(context::conf, "qt path {} doesn't exist", p); - - details::set_string("paths", "qt_install", path_to_utf8(p)); -} - -fs::path get_known_folder(const GUID& id) -{ - wchar_t* buffer = nullptr; - const auto r = ::SHGetKnownFolderPath(id, 0, 0, &buffer); - - if (r != S_OK) - return {}; - - fs::path p = buffer; - ::CoTaskMemFree(buffer); + const auto p = fs::canonical(third_party.parent_path()); + gcx().trace(context::conf, "found root directory at {}", p); return p; } +fs::path find_in_root(const fs::path& file) +{ + static fs::path root = find_root(); + + fs::path p = root / file; + if (!fs::exists(p)) + gcx().bail_out(context::conf, "{} not found", p); + + gcx().trace(context::conf, "found {}", p); + return p; +} + +fs::path find_third_party_directory() +{ + static fs::path path = find_in_root("third-party"); + return path; +} + fs::path find_program_files_x86() { fs::path p = get_known_folder(FOLDERID_ProgramFilesX86); @@ -313,6 +243,144 @@ fs::path find_program_files_x64() return p; } +fs::path find_vs() +{ + // asking vswhere + const auto output = vswhere::find_vs(); + if (output.empty()) + gcx().bail_out(context::conf, "vswhere failed"); + + const auto lines = split(path_to_utf8(output), "\r\n"); + + if (lines.empty()) + { + gcx().bail_out(context::conf, "vswhere didn't output anything"); + } + else if (lines.size() > 1) + { + gcx().error(context::conf, "vswhere returned multiple installations:"); + + for (auto&& line : lines) + gcx().error(context::conf, " - {}", line); + + gcx().bail_out(context::conf, + "specify the `vs` path in the `[paths]` section of the INI, or " + "pass -s paths/vs=PATH` to pick an installation"); + } + else + { + // only one line + const fs::path path(output); + + if (!fs::exists(path)) + { + gcx().bail_out(context::conf, + "the path given by vswhere doesn't exist: {}", path); + } + + return path; + } +} + +fs::path find_qt() +{ + // check from the ini first + fs::path p = conf().path().qt_install(); + + if (!p.empty()) + { + p = fs::absolute(p); + + // check if qmake exists in there + if (find_qmake(p)) + { + p = fs::absolute(p.parent_path() / ".."); + return p; + } + + // fail early, don't try to guess if the user had something in the ini + gcx().bail_out(context::conf, "no qt install in {}", p); + } + + + // a list of possible location + std::deque locations = + { + conf().path().pf_x64(), + "C:\\", + "D:\\" + }; + + // look for qmake in PATH, which is in %qt%/version/msvc.../bin + fs::path qmake = find_in_path("qmake.exe"); + if (!qmake.empty()) + locations.push_front(qmake.parent_path() / "../../"); + + // look for qtcreator.exe in PATH, which is in %qt%/Tools/QtCreator/bin + fs::path qtcreator = find_in_path("qtcreator.exe"); + if (!qtcreator.empty()) + locations.push_front(qtcreator.parent_path() / "../../../"); + + // check each location + for (fs::path loc : locations) + { + loc = fs::absolute(loc); + + // check for qmake in there + if (find_qmake(loc)) + { + loc = fs::absolute(loc.parent_path() / ".."); + return loc; + } + } + + gcx().bail_out(context::conf, "can't find qt install"); +} + +fs::path find_iscc() +{ + // don't bother if the installer isn't enabled, it might fail anyway + if (!find_one_task("installer")->enabled()) + return {}; + + // check from the ini first, supports both relative and absolute + const auto iscc = conf().tool().get("iscc"); + + if (iscc.is_absolute()) + { + if (!fs::exists(iscc)) + { + gcx().bail_out(context::conf, + "{} doesn't exist (from ini, absolute path)", iscc); + } + + return iscc; + } + + // path is relative + + // check in PATH + fs::path p = find_in_path(path_to_utf8(iscc)); + if (fs::exists(p)) + return fs::canonical(fs::absolute(p)); + + // check known installation paths for a bunch of versions + for (int v : {5, 6, 7, 8}) + { + const fs::path inno_dir = fmt::format("Inno Setup {}", v); + + // check for both architectures + for (fs::path pf : {conf().path().pf_x86(), conf().path().pf_x64()}) + { + p = pf / inno_dir / iscc; + if (fs::exists(p)) + return fs::canonical(fs::absolute(p)); + } + } + + gcx().bail_out(context::conf, "can't find {} anywhere", iscc); +} + fs::path find_temp_dir() { const std::size_t buffer_size = MAX_PATH + 2; @@ -330,53 +398,9 @@ fs::path find_temp_dir() return p; } -fs::path find_vs() -{ - if (conf::dry()) - return vs::vswhere(); - - const auto path = vswhere::find_vs(); - if (path.empty()) - gcx().bail_out(context::conf, "vswhere failed"); - - const auto lines = split(path_to_utf8(path), "\r\n"); - - if (lines.empty()) - { - gcx().bail_out(context::conf, "vswhere didn't output anything"); - } - else if (lines.size() > 1) - { - gcx().error(context::conf, "vswhere returned multiple installations:"); - - for (auto&& line : lines) - gcx().error(context::conf, " - {}", line); - - gcx().bail_out(context::conf, - "specify the `vs` path in the `[paths]` section of the INI, or " - "pass -s paths/vs=PATH` to pick an installation"); - } - - if (!fs::exists(path)) - { - gcx().bail_out(context::conf, - "the path given by vswhere doesn't exist: {}", path); - } - - return path; -} - -bool try_vcvars(fs::path& bat) -{ - if (!fs::exists(bat)) - return false; - - bat = fs::canonical(fs::absolute(bat)); - return true; -} - -void find_vcvars() +fs::path find_vcvars() { + // check from the ini first fs::path bat = conf().tool().get("vcvars"); if (conf::dry()) @@ -384,27 +408,26 @@ void find_vcvars() if (bat.empty()) bat = "vcvars.bat"; - return; + return bat; } - else + + if (bat.empty()) { - if (bat.empty()) - { - bat = vs::installation_path() - / "VC" / "Auxiliary" / "Build" / "vcvarsall.bat"; - - if (!try_vcvars(bat)) - gcx().bail_out(context::conf, "vcvars not found at {}", bat); - } - else - { - if (!try_vcvars(bat)) - gcx().bail_out(context::conf, "vcvars not found at {}", bat); - } + // derive from vs installation + bat = vs::installation_path() + / "VC" / "Auxiliary" / "Build" / "vcvarsall.bat"; } - details::set_string("tools", "vcvars", path_to_utf8(bat)); + if (!fs::exists(bat)) + gcx().bail_out(context::conf, "vcvars not found at {}", bat); + + if (bat.is_relative()) + bat = fs::absolute(bat); + + bat = fs::canonical(bat); gcx().trace(context::conf, "using vcvars at {}", bat); + + return bat; } } // namespace diff --git a/src/core/paths.h b/src/core/paths.h index b63fd3d..68c8a81 100644 --- a/src/core/paths.h +++ b/src/core/paths.h @@ -3,20 +3,53 @@ namespace mob { -fs::path find_root(bool verbose=false); -fs::path find_in_root(const fs::path& file); - -fs::path find_third_party_directory(); -fs::path find_program_files_x86(); -fs::path find_program_files_x64(); -fs::path find_vs(); -fs::path find_qt(); -fs::path find_iscc(); -fs::path find_temp_dir(); - -void find_vcvars(); -void validate_qt(); - +// returns the path to mob.exe that's currently running, including filename; +// bails on failure +// fs::path mob_exe_path(); +// returns mob's root directory, contains third-party/, etc.; bails on failure +// +// this is not necessarily the parent of mob_exe_path(), it mob could be running +// from the build directory +// +fs::path find_root(bool verbose=false); + +// resolves the given relative path against find_root(); bails when not found +// +fs::path find_in_root(const fs::path& file); + + +// returns the absolute path of mob's third-party directory; bails when not +// found +// +fs::path find_third_party_directory(); + +// returns the absolute path to x86/x64 program files directory +// +fs::path find_program_files_x86(); +fs::path find_program_files_x64(); + +// returns the absolute path to visual studio's root directory, the one that +// contains Common7, VC, etc.; bails if not found +// +fs::path find_vs(); + +// returns the absolute path to Qt's root directory, the one that contains +// the msvc_xxx directory; bails if not found +// +fs::path find_qt(); + +// returns the absolute path to iscc.exe; bails if not found +// +fs::path find_iscc(); + +// returns the absolute path to the system's temp directory; bails on error +// +fs::path find_temp_dir(); + +// returns the absolute path to the vcvars batch file, bails if not found +// +fs::path find_vcvars(); + } // namespace