Files

488 lines
11 KiB
C++

#include <map>
#include <string>
#include <fstream>
#include <iostream>
#include <filesystem>
#include <iomanip>
#include <sstream>
#include <format>
#include <vector>
#include <regex>
#include <json/json.h>
#include <openssl/evp.h>
#include <sys/wait.h>
namespace fs = std::filesystem;
const std::string CONFIG_PATH = "/home/unknownobject/UNSWebServerCore/build/core_distribute.json";
// 编译阶段枚举
enum class BuildPhase
{
Configure,
Compile,
Link
};
// 辅助函数:显示标准文件复制进度条
void DisplayProgressBar(size_t current, size_t total)
{
if (total == 0)
return;
float progress = static_cast<float>(current) / total;
int bar_width = 30;
std::string bar = "";
int pos = static_cast<int>(bar_width * progress);
for (int i = 0; i < bar_width; ++i)
{
if (i < pos)
bar += "=";
else if (i == pos)
bar += ">";
else
bar += " ";
}
std::cout << std::format("\t[{}] {:.0f}%\r", bar, progress * 100.0);
std::cout.flush();
if (current == total)
std::cout << "\n";
}
// 辅助函数:显示带有阶段状态的编译进度条
void DisplayBuildStatus(BuildPhase phase, int percentage)
{
std::string phase_str = "";
if (phase == BuildPhase::Configure)
phase_str = "\033[33m[1/3] Configuring\033[0m";
else if (phase == BuildPhase::Compile)
phase_str = "\033[33m[2/3] Compiling \033[0m";
else if (phase == BuildPhase::Link)
phase_str = "\033[33m[3/3] Linking \033[0m";
if (phase == BuildPhase::Compile)
{
int bar_width = 20;
std::string bar = "";
int pos = static_cast<int>(bar_width * (percentage / 100.0));
for (int i = 0; i < bar_width; ++i)
{
if (i < pos)
bar += "=";
else if (i == pos)
bar += ">";
else
bar += " ";
}
std::cout << std::format("\t{} [{}] {}%\r", phase_str, bar, percentage);
}
else
std::cout << std::format("\t{} \r", phase_str);
std::cout.flush();
}
// 辅助函数:计算单个文件的 SHA256 哈希值
std::string CalculateSHA256(const fs::path& file_path)
{
std::ifstream file_in(file_path, std::ios::binary);
if (!file_in.is_open())
return "";
EVP_MD_CTX* context = EVP_MD_CTX_new();
if (context == nullptr)
return "";
if (EVP_DigestInit_ex(context, EVP_sha256(), nullptr) != 1)
{
EVP_MD_CTX_free(context);
return "";
}
char buffer[4096];
while ((file_in.read(buffer, sizeof(buffer))) || (file_in.gcount() > 0))
{
if (EVP_DigestUpdate(context, buffer, file_in.gcount()) != 1)
{
EVP_MD_CTX_free(context);
return "";
}
}
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int length = 0;
if (EVP_DigestFinal_ex(context, hash, &length) != 1)
{
EVP_MD_CTX_free(context);
return "";
}
EVP_MD_CTX_free(context);
std::stringstream ss;
for (unsigned int i = 0; i < length; ++i)
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
return ss.str();
}
bool ClearDirectory(const fs::path& dir)
{
std::error_code ec;
if (!fs::exists(dir, ec))
{
fs::create_directories(dir, ec);
if (ec)
return false;
}
else if (!fs::is_directory(dir, ec))
return false;
for (const auto& entry : fs::directory_iterator(dir, ec))
{
if (ec)
return false;
fs::remove_all(entry.path(), ec);
if (ec)
return false;
}
return true;
}
bool CheckSOAndCopy(const fs::path& so_src, const fs::path& so_dst)
{
std::error_code ec;
if (!fs::exists(so_src, ec))
return false;
if (!fs::is_directory(so_src, ec))
return false;
if (!fs::exists(so_dst, ec))
{
fs::create_directories(so_dst, ec);
if (ec)
return false;
}
size_t total_so = 0;
for (const auto& entry : fs::directory_iterator(so_src, ec))
{
if (ec)
return false;
bool is_reg = entry.is_regular_file(ec);
bool is_so = entry.path().extension() == ".so";
if ((is_reg) && (is_so))
total_so++;
}
size_t processed_so = 0;
DisplayProgressBar(processed_so, total_so);
for (const auto& entry : fs::directory_iterator(so_src, ec))
{
if (ec)
return false;
bool is_reg = entry.is_regular_file(ec);
bool is_so = entry.path().extension() == ".so";
if ((is_reg) && (is_so))
{
fs::path dst_file = so_dst / entry.path().filename();
if (!fs::exists(dst_file, ec))
{
fs::copy_file(entry.path(), dst_file, fs::copy_options::overwrite_existing, ec);
if (ec)
return false;
}
else
{
std::string src_hash = CalculateSHA256(entry.path());
std::string dst_hash = CalculateSHA256(dst_file);
if (src_hash.empty())
return false;
if (dst_hash.empty())
return false;
if (src_hash != dst_hash)
{
fs::copy_file(entry.path(), dst_file, fs::copy_options::overwrite_existing, ec);
if (ec)
return false;
}
}
processed_so++;
DisplayProgressBar(processed_so, total_so);
}
}
return true;
}
bool CopyAll(const fs::path& dst)
{
static const fs::path SRC_DIR = "/home/unknownobject/UNSWebServerCore/build/unswsc";
std::error_code ec;
if (!fs::exists(SRC_DIR, ec))
return false;
if (!fs::is_directory(SRC_DIR, ec))
return false;
size_t total_files = 0;
for (const auto& entry : fs::recursive_directory_iterator(SRC_DIR, ec))
{
if (ec)
return false;
if (entry.is_regular_file(ec))
total_files++;
}
size_t copied_files = 0;
DisplayProgressBar(copied_files, total_files);
for (const auto& entry : fs::recursive_directory_iterator(SRC_DIR, ec))
{
if (ec)
return false;
if (entry.is_regular_file(ec))
{
fs::path relative_path = fs::relative(entry.path(), SRC_DIR, ec);
if (ec)
return false;
fs::path target_path = dst / relative_path;
fs::create_directories(target_path.parent_path(), ec);
if (ec)
return false;
fs::copy_file(entry.path(), target_path, fs::copy_options::overwrite_existing, ec);
if (ec)
return false;
copied_files++;
DisplayProgressBar(copied_files, total_files);
}
}
return true;
}
std::map<std::string, fs::path> DecodeJson()
{
Json::Value root;
Json::Reader reader;
try
{
std::fstream file_in(CONFIG_PATH, std::ios::in);
if (!file_in.is_open())
return {};
if (!reader.parse(file_in, root, false))
return {};
if (!root.isArray())
return {};
std::map<std::string, fs::path> result;
for (const auto& data : root)
result.insert({ data["Program"].asString(), data["Path"].asString() });
return result;
}
catch (...)
{
return {};
}
}
bool CallRebuild(const fs::path& path)
{
fs::path script_dir = path / "build";
fs::path sh_path = script_dir / "rebuild.sh";
std::error_code ec;
bool is_exist = fs::exists(sh_path, ec);
if (!is_exist)
return false;
bool is_file = fs::is_regular_file(sh_path, ec);
if (!is_file)
return false;
std::string cmd_str = "cd \"" + script_dir.string() + "\" && bash ./rebuild.sh 2>&1";
FILE* pipe = popen(cmd_str.c_str(), "r");
if (pipe == nullptr)
return false;
char line_buffer[512];
int last_pct = -1;
BuildPhase current_phase = BuildPhase::Configure;
std::vector<std::string> output_logs;
// 初始拉起,显示第一阶段
DisplayBuildStatus(current_phase, 0);
while (fgets(line_buffer, sizeof(line_buffer), pipe) != nullptr)
{
std::string line(line_buffer);
output_logs.push_back(line);
// 核心状态机切相逻辑
BuildPhase old_phase = current_phase;
if (line.find("Building CXX object") != std::string::npos)
current_phase = BuildPhase::Compile;
else if (line.find("Linking CXX") != std::string::npos)
current_phase = BuildPhase::Link;
if (current_phase != old_phase)
DisplayBuildStatus(current_phase, 0);
size_t start_pos = line.find('[');
size_t percent_pos = line.find('%');
if ((start_pos != std::string::npos) && (percent_pos != std::string::npos))
{
if (start_pos < percent_pos)
{
std::string pct_str = line.substr(start_pos + 1, percent_pos - start_pos - 1);
try
{
int pct = std::stoi(pct_str);
if ((pct >= 0) && (pct <= 100))
{
if (pct != last_pct)
{
DisplayBuildStatus(current_phase, pct);
last_pct = pct;
}
}
}
catch (...)
{
}
}
}
}
int status = pclose(pipe);
int exit_code = WEXITSTATUS(status);
// 清理进度条行尾
std::cout << " \r";
if (exit_code != 0)
{
std::cout << std::format("\033[31m\t[ERROR] Rebuild Failed! Dump Log Below:\033[0m\n");
std::cout << std::format("\033[31m\t----------------------------------------\033[0m\n");
// 匹配实际的转义控制字节 (\x1b) 以及字面量字符串 (\\e 或 \\033)
static const std::regex ANSI_REGEX(R"((\x1b|\\e|\\033)\[[0-9;]*m)");
for (const auto& log_line : output_logs)
{
// 过滤掉所有残留的颜色控制字符,避免与外层高亮红冲突
std::string clean_line = std::regex_replace(log_line, ANSI_REGEX, "");
std::cout << std::format("\t\033[31m {}\033[0m", clean_line);
}
std::cout << std::format("\033[31m\t----------------------------------------\033[0m\n");
return false;
}
return true;
}
int main()
{
auto programs = DecodeJson();
if (programs.empty())
{
std::cout << "\033[31mConfig Decode Failed\033[0m\n";
return 0;
}
static const fs::path SRC_SO_DIR = "/home/unknownobject/UNSWebServerCore/build/so";
int deploy_success = 0;
int deploy_failure = 0;
int build_success = 0;
int build_failure = 0;
for (const auto& [prog_name, folder_path] : programs)
{
fs::path header = folder_path / "unswsc";
fs::path dynamic = folder_path / "so";
std::cout << std::format("Processing [\033[35m{}\033[0m]\n", prog_name);
bool deploy_ok = true;
if (!ClearDirectory(header))
{
std::cout << std::format("\t\033[31mFailed to Clear Directory [{}]\033[0m\n\n", header.string());
deploy_ok = false;
}
else
std::cout << std::format("\tHeader Directory Cleared.\n");
if (deploy_ok)
{
if (!CopyAll(header))
{
std::cout << std::format("\t\033[31mFailed to Copy Header to [{}]\033[0m\n\n", header.string());
deploy_ok = false;
}
else
std::cout << std::format("\tHeader Copy Finished.\n");
}
if (deploy_ok)
{
if (!CheckSOAndCopy(SRC_SO_DIR, dynamic))
{
std::cout << std::format("\t\033[31mFailed to Copy SO Library to [{}]\033[0m\n\n", dynamic.string());
deploy_ok = false;
}
else
std::cout << std::format("\tSO Library Copy Finished.\n");
}
if (deploy_ok)
deploy_success++;
else
{
deploy_failure++;
continue;
}
std::cout << std::format("\tTrigger Auto Rebuild...\n");
if (CallRebuild(folder_path))
{
std::cout << std::format("\t\033[32mRebuild Success.\033[0m\n");
build_success++;
}
else
{
std::cout << std::format("\t\033[31mRebuild Failed.\033[0m\n");
build_failure++;
}
std::cout << std::format("Done.\n\n");
}
std::cout << std::format("\033[36m==================== Statistics Summary ====================\033[0m\n");
std::cout << std::format("Deployment (Clear+Headers+SO): \033[32m{} Success\033[0m | \033[31m{} Failure\033[0m\n", deploy_success, deploy_failure);
std::cout << std::format("Compilation (CallRebuild): \033[32m{} Success\033[0m | \033[31m{} Failure\033[0m\n", build_success, build_failure);
std::cout << std::format("\033[36m============================================================\033[0m\n");
return 0;
}