mirror of
https://github.com/ModOrganizer2/mob.git
synced 2026-07-27 14:07:05 -07:00
648 lines
11 KiB
C++
648 lines
11 KiB
C++
#include "pch.h"
|
|||
|
|
|
||
|
|
namespace builder
|
||
|
|
{
|
||
|
|
|
||
|
|
namespace fs = std::filesystem;
|
||
|
|
|
||
|
|
class bailed {};
|
||
|
|
const bool dry = false;
|
||
|
|
const bool verbose = false;
|
||
|
|
|
||
|
|
|
||
|
|
std::string error_message(DWORD e)
|
||
|
|
{
|
||
|
|
return std::error_code(
|
||
|
|
static_cast<int>(e), std::system_category()).message();
|
||
|
|
}
|
||
|
|
|
||
|
|
void out(const std::string& s)
|
||
|
|
{
|
||
|
|
static std::mutex m;
|
||
|
|
std::scoped_lock lock(m);
|
||
|
|
std::cout << s << "\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
void out(const std::string& s, DWORD e)
|
||
|
|
{
|
||
|
|
out(s + ", " + error_message(e));
|
||
|
|
}
|
||
|
|
|
||
|
|
void out(const std::string& s, const std::error_code& e)
|
||
|
|
{
|
||
|
|
out(s + ", " + e.message());
|
||
|
|
}
|
||
|
|
|
||
|
|
template <class... Args>
|
||
|
|
[[noreturn]] void bail_out(Args&&... args)
|
||
|
|
{
|
||
|
|
out(std::forward<Args>(args)...);
|
||
|
|
throw bailed();
|
||
|
|
}
|
||
|
|
|
||
|
|
template <class... Args>
|
||
|
|
void error(Args&&... args)
|
||
|
|
{
|
||
|
|
out(std::forward<Args>(args)...);
|
||
|
|
}
|
||
|
|
|
||
|
|
template <class... Args>
|
||
|
|
void info(Args&&... args)
|
||
|
|
{
|
||
|
|
out(std::forward<Args>(args)...);
|
||
|
|
}
|
||
|
|
|
||
|
|
template <class... Args>
|
||
|
|
void debug(Args&&... args)
|
||
|
|
{
|
||
|
|
if (verbose)
|
||
|
|
out(std::forward<Args>(args)...);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
std::string redir_nul()
|
||
|
|
{
|
||
|
|
if (verbose)
|
||
|
|
return {};
|
||
|
|
else
|
||
|
|
return "> NUL";
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
std::string read_text_file(const fs::path& p)
|
||
|
|
{
|
||
|
|
debug("reading " + p.string());
|
||
|
|
|
||
|
|
std::string s;
|
||
|
|
|
||
|
|
std::ifstream in(p);
|
||
|
|
if (!in)
|
||
|
|
bail_out("can't read from " + p.string() + "'");
|
||
|
|
|
||
|
|
in.seekg(0, std::ios::end);
|
||
|
|
s.resize(static_cast<std::size_t>(in.tellg()));
|
||
|
|
in.seekg(0, std::ios::beg);
|
||
|
|
in.read(&s[0], static_cast<std::streamsize>(s.size()));
|
||
|
|
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
struct versions
|
||
|
|
{
|
||
|
|
static const std::string visual_studio() { return "2019"; }
|
||
|
|
static const std::string sevenzip() { return "19.00"; }
|
||
|
|
};
|
||
|
|
|
||
|
|
struct paths
|
||
|
|
{
|
||
|
|
static fs::path prefix() { return R"(C:\dev\projects\mobuild-out)"; }
|
||
|
|
static fs::path cache() { return prefix() / "downloads"; }
|
||
|
|
static fs::path build() { return prefix() / "build"; }
|
||
|
|
static fs::path install() { return prefix() / "install"; }
|
||
|
|
|
||
|
|
static fs::path install_dlls() { return install() / "dlls"; }
|
||
|
|
|
||
|
|
static fs::path program_files_x86()
|
||
|
|
{
|
||
|
|
static fs::path path = []
|
||
|
|
{
|
||
|
|
wchar_t* buffer = nullptr;
|
||
|
|
|
||
|
|
const auto r = ::SHGetKnownFolderPath(
|
||
|
|
FOLDERID_ProgramFilesX86, 0, 0, &buffer);
|
||
|
|
|
||
|
|
fs::path p;
|
||
|
|
|
||
|
|
if (r == S_OK)
|
||
|
|
{
|
||
|
|
p = buffer;
|
||
|
|
::CoTaskMemFree(buffer);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
const auto e = GetLastError();
|
||
|
|
error("failed to get program files folder", e);
|
||
|
|
p = fs::path(R"(C:\Program Files (x86))");
|
||
|
|
}
|
||
|
|
|
||
|
|
debug("program files is '" + p.string() + "'");
|
||
|
|
return p;
|
||
|
|
}();
|
||
|
|
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
static fs::path temp_dir()
|
||
|
|
{
|
||
|
|
static fs::path temp_dir = []
|
||
|
|
{
|
||
|
|
const std::size_t buffer_size = MAX_PATH + 2;
|
||
|
|
wchar_t buffer[buffer_size] = {};
|
||
|
|
|
||
|
|
if (GetTempPathW(static_cast<DWORD>(buffer_size), buffer) == 0)
|
||
|
|
{
|
||
|
|
const auto e = GetLastError();
|
||
|
|
bail_out("can't get temp path", e);
|
||
|
|
}
|
||
|
|
|
||
|
|
fs::path p(buffer);
|
||
|
|
debug("temp dir is " + p.string());
|
||
|
|
return p;
|
||
|
|
}();
|
||
|
|
|
||
|
|
return temp_dir;
|
||
|
|
}
|
||
|
|
|
||
|
|
static fs::path temp_file()
|
||
|
|
{
|
||
|
|
static fs::path dir = temp_dir();
|
||
|
|
|
||
|
|
wchar_t name[MAX_PATH + 1] = {};
|
||
|
|
if (GetTempFileNameW(dir.native().c_str(), L"mo_", 0, name) == 0)
|
||
|
|
{
|
||
|
|
const auto e = GetLastError();
|
||
|
|
bail_out("can't create temp file in " + dir.string(), e);
|
||
|
|
}
|
||
|
|
|
||
|
|
return dir / name;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class op
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
static void create_directories(const fs::path& p)
|
||
|
|
{
|
||
|
|
debug("creating directory " + p.string());
|
||
|
|
check(p);
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
do_create_directories(p);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void delete_directory(const fs::path& p)
|
||
|
|
{
|
||
|
|
debug("deleting directory " + p.string());
|
||
|
|
check(p);
|
||
|
|
|
||
|
|
if (fs::exists(p) && !fs::is_directory(p))
|
||
|
|
bail_out(p.string() + " is not a directory");
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
do_delete_directory(p);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void delete_file(const fs::path& p)
|
||
|
|
{
|
||
|
|
debug("deleting file " + p.string());
|
||
|
|
check(p);
|
||
|
|
|
||
|
|
if (fs::exists(p) && !fs::is_regular_file(p))
|
||
|
|
bail_out("can't delete " + p.string() + ", not a file");
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
do_delete_file(p);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void remove_readonly(const fs::path& first)
|
||
|
|
{
|
||
|
|
debug("removing read-only from " + first.string());
|
||
|
|
check(first);
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
{
|
||
|
|
if (fs::is_regular_file(first))
|
||
|
|
do_remove_readonly(first);
|
||
|
|
|
||
|
|
for (auto&& p : fs::recursive_directory_iterator(first))
|
||
|
|
{
|
||
|
|
if (fs::is_regular_file(p))
|
||
|
|
do_remove_readonly(p);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void copy_file_to_dir(const fs::path& file, const fs::path& dir)
|
||
|
|
{
|
||
|
|
debug("copying " + file.string() + " to " + dir.string());
|
||
|
|
|
||
|
|
check(file);
|
||
|
|
check(dir);
|
||
|
|
|
||
|
|
if (!fs::exists(file) || !fs::is_regular_file(file))
|
||
|
|
bail_out("can't copy " + file.string() + ", not a file");
|
||
|
|
|
||
|
|
if (fs::exists(dir) && !fs::is_directory(dir))
|
||
|
|
bail_out("can't copy to " + dir.string() + ", not a directory");
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
{
|
||
|
|
op::create_directories(dir);
|
||
|
|
do_copy_file_to_dir(file, dir);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void run(const std::string& cmd, const fs::path& cwd={})
|
||
|
|
{
|
||
|
|
debug("> " + cmd);
|
||
|
|
|
||
|
|
if (!dry)
|
||
|
|
{
|
||
|
|
const int r = do_run(cmd, cwd);
|
||
|
|
if (r != 0)
|
||
|
|
bail_out("command returned " + std::to_string(r));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
static void do_create_directories(const fs::path& p)
|
||
|
|
{
|
||
|
|
std::error_code ec;
|
||
|
|
fs::create_directories(p, ec);
|
||
|
|
|
||
|
|
if (ec)
|
||
|
|
bail_out("can't create " + p.string(), ec);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void do_delete_directory(const fs::path& p)
|
||
|
|
{
|
||
|
|
if (!fs::exists(p))
|
||
|
|
return;
|
||
|
|
|
||
|
|
std::error_code ec;
|
||
|
|
fs::remove_all(p, ec);
|
||
|
|
|
||
|
|
if (ec)
|
||
|
|
{
|
||
|
|
if (ec.value() == ERROR_ACCESS_DENIED)
|
||
|
|
{
|
||
|
|
remove_readonly(p);
|
||
|
|
fs::remove_all(p, ec);
|
||
|
|
|
||
|
|
if (!ec)
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
bail_out("failed to delete " + p.string(), ec);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void do_delete_file(const fs::path& p)
|
||
|
|
{
|
||
|
|
std::error_code ec;
|
||
|
|
fs::remove(p, ec);
|
||
|
|
|
||
|
|
if (ec)
|
||
|
|
bail_out("can't delete " + p.string(), ec);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void do_copy_file_to_dir(const fs::path& f, const fs::path& d)
|
||
|
|
{
|
||
|
|
std::error_code ec;
|
||
|
|
|
||
|
|
fs::copy_file(
|
||
|
|
f, d / f.filename(),
|
||
|
|
fs::copy_options::overwrite_existing, ec);
|
||
|
|
|
||
|
|
if (ec)
|
||
|
|
bail_out("can't copy " + f.string() + " to " + d.string(), ec);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void do_remove_readonly(const fs::path& p)
|
||
|
|
{
|
||
|
|
std::error_code ec;
|
||
|
|
fs::permissions(p, fs::perms::owner_write, fs::perm_options::add, ec);
|
||
|
|
|
||
|
|
if (ec)
|
||
|
|
bail_out("can't remove read-only flag on " + p.string(), ec);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int do_run(const std::string& cmd, const fs::path& cwd)
|
||
|
|
{
|
||
|
|
if (cwd.empty())
|
||
|
|
return std::system(cmd.c_str());
|
||
|
|
else
|
||
|
|
return std::system(("cd \"" + cwd.string() + "\" && " + cmd).c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
static void check(const fs::path& p)
|
||
|
|
{
|
||
|
|
if (p.empty())
|
||
|
|
bail_out("path is empty");
|
||
|
|
|
||
|
|
if (p.native().starts_with(paths::prefix().native()))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (p.native().starts_with(paths::temp_dir().native()))
|
||
|
|
return;
|
||
|
|
|
||
|
|
bail_out("path " + p.string() + " is outside prefix");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class url
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
url(const char* p)
|
||
|
|
: s_(p)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
url(std::string s={})
|
||
|
|
: s_(std::move(s))
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
const char* c_str() const
|
||
|
|
{
|
||
|
|
return s_.c_str();
|
||
|
|
}
|
||
|
|
|
||
|
|
const std::string& string() const
|
||
|
|
{
|
||
|
|
return s_;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string file() const
|
||
|
|
{
|
||
|
|
const auto pos = s_.find_last_of("/");
|
||
|
|
|
||
|
|
if (pos == std::string::npos)
|
||
|
|
return s_;
|
||
|
|
else
|
||
|
|
return s_.substr(pos + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::string s_;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
class downloader
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
downloader(fs::path where, url u)
|
||
|
|
: where_(std::move(where)), url_(std::move(u)), file_(nullptr)
|
||
|
|
{
|
||
|
|
path_ = where_ / url_.file();
|
||
|
|
}
|
||
|
|
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
debug("downloading " + url_.string());
|
||
|
|
|
||
|
|
op::create_directories(where_);
|
||
|
|
|
||
|
|
if (fs::exists(path_))
|
||
|
|
{
|
||
|
|
debug("download " + path_.string() + " already exists");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
thread_ = std::thread([&]{ run(); });
|
||
|
|
}
|
||
|
|
|
||
|
|
void join()
|
||
|
|
{
|
||
|
|
if (thread_.joinable())
|
||
|
|
thread_.join();
|
||
|
|
}
|
||
|
|
|
||
|
|
fs::path file() const
|
||
|
|
{
|
||
|
|
return path_;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
fs::path where_;
|
||
|
|
url url_;
|
||
|
|
fs::path path_;
|
||
|
|
std::FILE* file_;
|
||
|
|
std::thread thread_;
|
||
|
|
|
||
|
|
void run()
|
||
|
|
{
|
||
|
|
auto* c = curl_easy_init();
|
||
|
|
|
||
|
|
curl_easy_setopt(c, CURLOPT_URL, url_.c_str());
|
||
|
|
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, on_write_static);
|
||
|
|
curl_easy_setopt(c, CURLOPT_WRITEDATA, this);
|
||
|
|
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1l);
|
||
|
|
|
||
|
|
|
||
|
|
const auto r = curl_easy_perform(c);
|
||
|
|
|
||
|
|
if (r == 0)
|
||
|
|
{
|
||
|
|
long h = 0;
|
||
|
|
curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &h);
|
||
|
|
|
||
|
|
if (h != 200)
|
||
|
|
bail_out(url_.string() + " http code: " + std::to_string(h));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
bail_out(url_.string() + "curl code: " + std::to_string(r));
|
||
|
|
}
|
||
|
|
|
||
|
|
curl_easy_cleanup(c);
|
||
|
|
|
||
|
|
if (file_)
|
||
|
|
std::fclose(file_);
|
||
|
|
}
|
||
|
|
|
||
|
|
static size_t on_write_static(
|
||
|
|
char* ptr, size_t size, size_t nmemb, void* user) noexcept
|
||
|
|
{
|
||
|
|
auto* self = static_cast<downloader*>(user);
|
||
|
|
self->on_write(ptr, size * nmemb);
|
||
|
|
return size * nmemb;
|
||
|
|
}
|
||
|
|
|
||
|
|
void on_write(char* ptr, std::size_t n) noexcept
|
||
|
|
{
|
||
|
|
if (!file_)
|
||
|
|
file_ = _wfopen(path_.native().c_str(), L"wb");
|
||
|
|
|
||
|
|
std::fwrite(ptr, n, 1, file_);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
fs::path download(const url& u)
|
||
|
|
{
|
||
|
|
downloader d(paths::cache(), u);
|
||
|
|
|
||
|
|
d.start();
|
||
|
|
d.join();
|
||
|
|
|
||
|
|
return d.file();
|
||
|
|
}
|
||
|
|
|
||
|
|
fs::path find_sevenz()
|
||
|
|
{
|
||
|
|
static fs::path path = []
|
||
|
|
{
|
||
|
|
const fs::path sevenz = "7z.exe";
|
||
|
|
const fs::path third_party = "third-party/bin";
|
||
|
|
|
||
|
|
fs::path final;
|
||
|
|
|
||
|
|
if (fs::path p=third_party / sevenz; fs::exists(p))
|
||
|
|
final = fs::absolute(p);
|
||
|
|
else if(p = ".." / third_party / sevenz; fs::exists(p))
|
||
|
|
final = fs::absolute(p);
|
||
|
|
else if (p = "../../.." / third_party / sevenz; fs::exists(p))
|
||
|
|
final = fs::absolute(p);
|
||
|
|
|
||
|
|
if (final.empty())
|
||
|
|
bail_out("7z.exe not found");
|
||
|
|
|
||
|
|
debug("found " + final.string());
|
||
|
|
return final;
|
||
|
|
}();
|
||
|
|
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
void expand(const fs::path& file, const fs::path& where)
|
||
|
|
{
|
||
|
|
if (fs::exists(where))
|
||
|
|
{
|
||
|
|
debug(
|
||
|
|
"not expanding " + file.string() + ", " +
|
||
|
|
where.string() + " already exists");
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const auto sevenz = find_sevenz();
|
||
|
|
|
||
|
|
op::delete_directory(where);
|
||
|
|
op::create_directories(where);
|
||
|
|
|
||
|
|
op::run(
|
||
|
|
sevenz.string() + " "
|
||
|
|
"x "
|
||
|
|
"-bd "
|
||
|
|
"-bb0 "
|
||
|
|
"-o" + where.string() + " "
|
||
|
|
"\"" + file.string() + "\" " +
|
||
|
|
redir_nul());
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class sevenz
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
void run()
|
||
|
|
{
|
||
|
|
info("7z");
|
||
|
|
|
||
|
|
const auto file = download("https://www.7-zip.org/a/7z1900-src.7z");
|
||
|
|
|
||
|
|
const auto dir = paths::build() / "7zip-19.00";
|
||
|
|
expand(file, dir);
|
||
|
|
|
||
|
|
const fs::path src = dir / "CPP" / "7zip" / "Bundles" / "Format7zF";
|
||
|
|
|
||
|
|
op::run(std::string("nmake ") +
|
||
|
|
(verbose ? "" : "/C /S ") +
|
||
|
|
"/NOLOGO CPU=x64 NEW_COMPILER=1 "
|
||
|
|
"MY_STATIC_LINK=1 NO_BUFFEROVERFLOWU=1 ",
|
||
|
|
src);
|
||
|
|
|
||
|
|
op::copy_file_to_dir(src / "x64/7z.dll", paths::install_dlls());
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
fs::path find_vcvars()
|
||
|
|
{
|
||
|
|
const std::vector<std::string> editions =
|
||
|
|
{
|
||
|
|
"Preview", "Enterprise", "Professional", "Community"
|
||
|
|
};
|
||
|
|
|
||
|
|
for (auto&& edition : editions)
|
||
|
|
{
|
||
|
|
const auto p =
|
||
|
|
paths::program_files_x86() /
|
||
|
|
"Microsoft Visual Studio" /
|
||
|
|
versions::visual_studio() /
|
||
|
|
edition / "VC" / "Auxiliary" / "Build" / "vcvarsall.bat";
|
||
|
|
|
||
|
|
if (fs::exists(p))
|
||
|
|
{
|
||
|
|
debug("found " + p.string());
|
||
|
|
return p;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bail_out("couldn't find visual studio");
|
||
|
|
}
|
||
|
|
|
||
|
|
void vcvars()
|
||
|
|
{
|
||
|
|
const fs::path tmp = paths::temp_file();
|
||
|
|
|
||
|
|
const std::string cmd =
|
||
|
|
"\"" + find_vcvars().string() + "\" amd64 " + redir_nul() + " && "
|
||
|
|
"set > " + tmp.string();
|
||
|
|
|
||
|
|
op::run(cmd);
|
||
|
|
|
||
|
|
std::stringstream ss(read_text_file(tmp));
|
||
|
|
op::delete_file(tmp);
|
||
|
|
|
||
|
|
for (;;)
|
||
|
|
{
|
||
|
|
std::string line;
|
||
|
|
std::getline(ss, line);
|
||
|
|
if (!ss)
|
||
|
|
break;
|
||
|
|
|
||
|
|
const auto sep = line.find('=');
|
||
|
|
|
||
|
|
if (sep == std::string::npos)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
const std::string name = line.substr(0, sep);
|
||
|
|
const std::string value = line.substr(sep + 1);
|
||
|
|
|
||
|
|
debug("setting env " + name);
|
||
|
|
SetEnvironmentVariableA(name.c_str(), nullptr);
|
||
|
|
SetEnvironmentVariableA(name.c_str(), value.c_str());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int run()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
vcvars();
|
||
|
|
sevenz().run();
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
catch(bailed)
|
||
|
|
{
|
||
|
|
error("bailing out");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
|
||
|
|
int main()
|
||
|
|
{
|
||
|
|
int r = builder::run();
|
||
|
|
std::cin.get();
|
||
|
|
return r;
|
||
|
|
}
|