Files
mob/src/cmd/release.cpp
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

533 lines
18 KiB
C++
Raw Normal View History

2020-11-09 02:46:33 -05:00
#include "pch.h"
#include "../core/conf.h"
#include "../core/context.h"
2021-02-06 17:11:22 -05:00
#include "../core/ini.h"
2020-11-09 02:46:33 -05:00
#include "../core/op.h"
2020-11-30 06:03:39 -05:00
#include "../tasks/task_manager.h"
#include "../tasks/tasks.h"
2020-11-09 02:46:33 -05:00
#include "../utility.h"
#include "../utility/threading.h"
#include "commands.h"
namespace mob {
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void set_sigint_handler();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
release_command::release_command() : command(requires_options) {}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
command::meta_t release_command::meta() const
{
return {"release", "creates a release"};
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void release_command::make_bin()
{
const auto out = out_ / make_filename("");
u8cout << "making binary archive " << path_to_utf8(out) << "\n";
2023-09-19 18:28:24 +02:00
2020-11-21 00:39:41 -05:00
op::archive_from_glob(gcx(), conf().path().install_bin() / "*", out,
{"__pycache__"});
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void release_command::make_pdbs()
{
const auto out = out_ / make_filename("pdbs");
u8cout << "making pdbs archive " << path_to_utf8(out) << "\n";
2023-09-19 18:28:24 +02:00
2020-11-21 00:39:41 -05:00
op::archive_from_glob(gcx(), conf().path().install_pdbs() / "*", out,
{"__pycache__"});
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void release_command::make_src()
{
const auto out = out_ / make_filename("src");
u8cout << "making src archive " << path_to_utf8(out) << "\n";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
const std::vector<std::string> ignore = {"\\..+", // dot files
".*\\.log", ".*\\.tlog", ".*\\.dll",
".*\\.exe", ".*\\.lib", ".*\\.obj",
".*\\.ts", ".*\\.aps", "vsbuild"};
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
const std::vector<std::regex> ignore_re(ignore.begin(), ignore.end());
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::vector<fs::path> files;
std::size_t total_size = 0;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!fs::exists(tasks::modorganizer::super_path())) {
gcx().bail_out(context::generic, "modorganizer super path not found: {}",
2020-11-18 00:42:43 -05:00
tasks::modorganizer::super_path());
2023-09-19 18:28:24 +02:00
}
2020-11-18 00:42:43 -05:00
// build list list
walk_dir(tasks::modorganizer::super_path(), files, ignore_re, total_size);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// should be below 20MB
2020-11-18 00:42:43 -05:00
const std::size_t max_expected_size = 20 * 1024 * 1024;
if (total_size >= max_expected_size) {
2020-11-09 02:46:33 -05:00
gcx().warning(context::generic,
"total size of source files would be {}, expected something "
"below {}, something might be wrong",
total_size, max_expected_size);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!force_) {
gcx().bail_out(context::generic, "bailing out, use --force to ignore");
}
2023-09-19 18:28:24 +02:00
}
2020-11-18 00:42:43 -05:00
op::archive_from_files(gcx(), files, tasks::modorganizer::super_path(), out);
2023-09-19 18:28:24 +02:00
}
2020-11-09 02:46:33 -05:00
void release_command::make_uibase()
2023-09-19 18:28:24 +02:00
{
2020-11-09 02:46:33 -05:00
const auto out = out_ / make_filename("uibase");
u8cout << "making uibase archive " << path_to_utf8(out) << "\n";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::vector<fs::path> files;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!fs::exists(tasks::modorganizer::super_path())) {
gcx().bail_out(context::generic, "modorganizer super path not found: {}",
2020-11-18 00:42:43 -05:00
tasks::modorganizer::super_path());
2023-09-19 18:28:24 +02:00
}
op::archive_from_glob(
gcx(), tasks::modorganizer::super_path() / "uibase" / "src" / "*.h", out,
2023-09-19 18:28:24 +02:00
{});
2020-11-18 00:42:43 -05:00
op::archive_from_files(gcx(), {conf().path().install_libs() / "uibase.lib"},
conf().path().install_libs(), out);
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
void release_command::make_installer()
{
const auto file = "Mod.Organizer-" + version_ + ".exe";
const auto src = conf().path().install_installer() / file;
const auto dest = out_;
2023-09-19 18:28:24 +02:00
u8cout << "copying installer " << file << "\n";
2023-09-19 18:28:24 +02:00
op::copy_file_to_dir_if_better(gcx(), src, dest);
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void release_command::walk_dir(const fs::path& dir, std::vector<fs::path>& files,
const std::vector<std::regex>& ignore_re,
std::size_t& total_size)
2023-09-19 18:28:24 +02:00
{
2020-11-09 02:46:33 -05:00
// adds all files that are not in the ignore list to `files`, recursive
2023-09-19 18:28:24 +02:00
2020-11-21 00:39:41 -05:00
for (auto e : fs::directory_iterator(dir)) {
const auto p = e.path();
const auto filename = path_to_utf8(p.filename());
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
bool ignored = false;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
for (auto&& re : ignore_re) {
if (std::regex_match(filename, re)) {
ignored = true;
2023-09-19 18:28:24 +02:00
break;
}
}
2020-11-09 02:46:33 -05:00
if (ignored)
continue;
2023-09-19 18:28:24 +02:00
if (e.is_directory()) {
walk_dir(e.path(), files, ignore_re, total_size);
2020-11-09 02:46:33 -05:00
}
else if (e.is_regular_file()) {
total_size += fs::file_size(p);
files.push_back(p);
2023-09-19 18:28:24 +02:00
}
}
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
fs::path release_command::make_filename(const std::string& what) const
{
std::string filename = "Mod.Organizer";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!version_.empty())
filename += "-" + version_;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!suffix_.empty())
filename += "-" + suffix_;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!what.empty())
filename += "-" + what;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
filename += ".7z";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
return filename;
2023-09-19 18:28:24 +02:00
}
2020-11-09 02:46:33 -05:00
clipp::group release_command::do_group()
{
return clipp::group(
clipp::command("release").set(picked_),
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("-h", "--help") >> help_) % ("shows this message"),
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
"devbuild" %
(clipp::command("devbuild").set(mode_, modes::devbuild),
(clipp::option("--bin").set(bin_, true) |
clipp::option("--no-bin").set(bin_, false)) %
"sets whether the binary archive is created [default: yes]",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--pdbs").set(pdbs_, true) |
clipp::option("--no-pdbs").set(pdbs_, false)) %
"sets whether the PDBs archive is created [default: yes]",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--src").set(src_, true) |
clipp::option("--no-src").set(src_, false)) %
"sets whether the source archive is created [default: yes]",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
//(
// clipp::option("--uibase").set(uibase_, true) |
// clipp::option("--no-uibase").set(uibase_, false)
//) % "sets whether the uibase archive is created [default: yes]",
2023-09-19 18:28:24 +02:00
(clipp::option("--inst").set(installer_, true) |
clipp::option("--no-inst").set(installer_, false)) %
"sets whether the installer is copied [default: no]",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
clipp::option("--version-from-exe").set(version_exe_) %
"retrieves version information from ModOrganizer.exe "
"[default]",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
clipp::option("--version-from-rc").set(version_rc_) %
"retrieves version information from "
"modorganizer/src/version.rc",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--rc") & clipp::value("PATH") >> utf8_rc_path_) %
"overrides the path to version.rc",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--version") &
clipp::value("VERSION") >> version_) %
"overrides the version string",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--output-dir") &
clipp::value("PATH") >> utf8out_) %
"sets the output directory to use instead of "
"`$prefix/releases`",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
(clipp::option("--suffix") & clipp::value("SUFFIX") >> suffix_) %
"optional suffix to add to the archive filenames",
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
clipp::option("--force").set(force_) %
"ignores file size warnings and existing release directories")
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
|
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
"official" % (clipp::command("official").set(mode_, modes::official),
(clipp::value("branch") >> branch_) %
"use this branch in the super repos"));
2023-09-19 18:28:24 +02:00
}
2020-11-09 02:46:33 -05:00
void release_command::convert_cl_to_conf()
2023-09-19 18:28:24 +02:00
{
2020-11-09 02:46:33 -05:00
command::convert_cl_to_conf();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (mode_ == modes::official) {
// force enable translations, installer and tx
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
common.options.push_back("task/mo_branch=" + branch_);
common.options.push_back("translations:task/enabled=true");
2020-11-09 02:46:33 -05:00
common.options.push_back("installer:task/enabled=true");
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
common.options.push_back("transifex/force=true");
common.options.push_back("transifex/configure=true");
common.options.push_back("transifex/pull=true");
2023-09-19 18:28:24 +02:00
}
}
2020-11-09 02:46:33 -05:00
int release_command::do_run()
2023-09-19 18:28:24 +02:00
{
2020-11-09 02:46:33 -05:00
switch (mode_) {
case modes::devbuild:
return do_devbuild();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
case modes::official:
return do_official();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
case modes::none:
default:
u8cerr << "bad release mode " << static_cast<int>(mode_) << "\n";
throw bailed();
}
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
int release_command::do_devbuild()
{
prepare();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
u8cout
<< ">> don't forget to update the version number before making a release\n"
<< "\n"
<< "creating release for " << version_ << "\n";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (bin_)
make_bin();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (pdbs_)
make_pdbs();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (src_)
make_src();
2023-09-19 18:28:24 +02:00
if (uibase_)
make_uibase();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (installer_)
make_installer();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
return 0;
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
int release_command::do_official()
{
set_sigint_handler();
2023-09-19 18:28:24 +02:00
// make sure the given branch exists in all repos, this avoids failure
// much later on in the process; throws on failure
check_repos_for_branch();
2023-09-19 18:28:24 +02:00
// if the prefix exists, asks the user to delete it
if (!check_clean_prefix())
return 1;
2023-09-19 18:28:24 +02:00
2020-11-30 06:03:39 -05:00
task_manager::instance().run_all();
build_command::terminate_msbuild();
2023-09-19 18:28:24 +02:00
prepare();
make_bin();
make_pdbs();
make_src();
make_uibase();
make_installer();
2023-09-19 18:28:24 +02:00
return 0;
}
2023-09-19 18:28:24 +02:00
void release_command::check_repos_for_branch()
{
2020-11-09 02:46:33 -05:00
u8cout << "checking repos for branch " << branch_ << "...\n";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
thread_pool tp;
std::atomic<bool> failed = false;
2023-09-19 18:28:24 +02:00
2020-11-30 06:03:39 -05:00
for (const auto* t : task_manager::instance().find("super")) {
2020-11-09 02:46:33 -05:00
if (!t->enabled())
continue;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
tp.add([this, t, &failed] {
2020-11-18 00:42:43 -05:00
const auto* o = dynamic_cast<const tasks::modorganizer*>(t);
2023-09-19 18:28:24 +02:00
2020-11-25 10:56:18 -05:00
if (!git_wrap::remote_branch_exists(o->git_url(), branch_)) {
2020-11-09 02:46:33 -05:00
gcx().error(context::generic,
"branch {} doesn't exist in the {} repo", branch_,
o->name());
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
failed = true;
}
});
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
tp.join();
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (failed) {
gcx().bail_out(context::generic,
"either fix the branch name, create a remote branch for the "
"repos that don't have it, or disable tasks with "
"`-s TASKNAME:task/enabled=false`");
2023-09-19 18:28:24 +02:00
}
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
bool release_command::check_clean_prefix()
{
2020-11-21 00:39:41 -05:00
const auto prefix = conf().path().prefix();
2023-09-19 18:28:24 +02:00
2020-11-21 00:08:45 -05:00
if (!fs::exists(prefix))
return true;
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
bool saw_file = false;
const fs::path log_file = conf().global().get("log_file");
const std::string ini_file = default_ini_filename();
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
for (auto itor : fs::directory_iterator(prefix)) {
const auto name = itor.path().filename();
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
// ignore ini and logs
if (name == log_file.filename() || name == ini_file)
continue;
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
saw_file = true;
break;
}
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
if (!saw_file) {
// empty directory, that's fine
return true;
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
const auto q =
fmt::format("prefix {} already exists, delete?", path_to_utf8(prefix));
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
if (ask_yes_no(q, yn::no) != yn::yes)
return false;
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
// the log file might be in this directory, close it now and reopen it
// when deletion is finished
context::close_log_file();
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
build_command::terminate_msbuild();
op::delete_directory(gcx(), prefix);
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
// reopen log file
conf().set_log_file();
2023-09-19 18:28:24 +02:00
2021-02-06 17:11:22 -05:00
return true;
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void release_command::prepare()
{
// finding rc file
2020-11-09 02:46:33 -05:00
rc_path_ = fs::path(utf8_to_utf16(utf8_rc_path_));
if (rc_path_.empty()) {
2020-11-18 00:42:43 -05:00
rc_path_ = tasks::modorganizer::super_path() / "modorganizer" / "src" /
"version.rc";
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
// getting version from rc or exe
2020-11-09 02:46:33 -05:00
if (version_.empty()) {
if (version_rc_)
version_ = version_from_rc();
else
version_ = version_from_exe();
}
2023-09-19 18:28:24 +02:00
// finding output path
2020-11-21 00:39:41 -05:00
const auto prefix = conf().path().prefix();
2020-11-09 02:46:33 -05:00
out_ = fs::path(utf8_to_utf16(utf8out_));
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (out_.empty())
2020-11-21 00:08:45 -05:00
out_ = prefix / "releases" / version_;
2020-11-09 02:46:33 -05:00
else if (out_.is_relative())
2020-11-21 00:08:45 -05:00
out_ = prefix / out_;
2020-11-09 02:46:33 -05:00
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::string release_command::do_doc()
{
return "Creates archives for an MO installation, PDBs and sources.\n"
"\n"
"Commands:\n"
"devbuild\n"
" Can creates three archives in `$prefix/releases/version`: one from\n"
" `install/bin/*`, one from `install/pdbs/*` and another with the\n"
" sources of projects from modorganizer_super.\n"
" \n"
" The archive filename is `Mod.Organizer-version-suffix-what.7z`,\n"
" where:\n"
" - `version` is taken from `ModOrganizer.exe`, `version.rc`\n"
" or from --version;\n"
" - `suffix` is the optional `--suffix` argument;\n"
" - `what` is either nothing, `src` or `pdbs`.\n"
"\n"
"official\n"
" Creates a new full build in the prefix. Requires that directory\n"
" to be empty. Puts the binary archive, source, PDBs and installer\n"
" in `$prefix/releases/version`. Forces all tasks to be enabled,\n"
" including translations and installer. Make sure the transifex API\n"
" key is in the INI or TX_TOKEN is set.";
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::string release_command::version_from_exe() const
{
2020-11-21 00:39:41 -05:00
const auto exe = conf().path().install_bin() / "ModOrganizer.exe";
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// getting version info size
DWORD dummy = 0;
const DWORD size = GetFileVersionInfoSizeW(exe.native().c_str(), &dummy);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (size == 0) {
const auto e = GetLastError();
gcx().bail_out(context::generic,
"can't get file version info size from {}, {}", exe,
error_message(e));
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// getting version info
auto buffer = std::make_unique<std::byte[]>(size);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!GetFileVersionInfoW(exe.native().c_str(), 0, size, buffer.get())) {
const auto e = GetLastError();
gcx().bail_out(context::generic, "can't get file version info from {}, {}",
exe, error_message(e));
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
struct LANGANDCODEPAGE {
WORD wLanguage;
WORD wCodePage;
};
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
void* value_pointer = nullptr;
unsigned int value_size = 0;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// getting list of available languages
auto ret = VerQueryValueW(buffer.get(), L"\\VarFileInfo\\Translation",
&value_pointer, &value_size);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!ret || !value_pointer || value_size == 0) {
const auto e = GetLastError();
gcx().bail_out(context::generic,
"VerQueryValueW() for translations failed on {}, {}", exe,
error_message(e));
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// number of languages
const auto count = value_size / sizeof(LANGANDCODEPAGE);
if (count == 0)
gcx().bail_out(context::generic, "no languages found in {}", exe);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// using the first language in the list to get FileVersion
const auto* lcp = static_cast<LANGANDCODEPAGE*>(value_pointer);
2023-09-19 18:28:24 +02:00
2020-11-18 00:42:43 -05:00
const auto sub_block =
2020-11-09 02:46:33 -05:00
fmt::format(L"\\StringFileInfo\\{:04x}{:04x}\\FileVersion", lcp->wLanguage,
lcp->wCodePage);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
ret = VerQueryValueW(buffer.get(), sub_block.c_str(), &value_pointer,
&value_size);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (!ret || !value_pointer || value_size == 0) {
gcx().bail_out(context::generic, "language {} not found in {}", sub_block,
exe);
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
// value_size includes the null terminator
return utf16_to_utf8(
std::wstring(static_cast<wchar_t*>(value_pointer), value_size - 1));
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::string release_command::version_from_rc() const
{
// matching: #define VER_FILEVERSION_STR "2.2.1\0"
std::regex re(R"(#define VER_FILEVERSION_STR "(.+)\\0")");
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
const std::string rc = op::read_text_file(gcx(), encodings::utf8, rc_path_);
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
std::smatch m;
std::string v;
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
for_each_line(rc, [&](std::string_view line) {
std::string line_s(line);
if (std::regex_match(line_s, m, re))
v = m[1];
});
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
if (v.empty()) {
gcx().bail_out(context::generic, "can't find version string in {}",
rc_path_);
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
return v;
}
2023-09-19 18:28:24 +02:00
2020-11-09 02:46:33 -05:00
} // namespace mob