fix log error, add color log
This commit is contained in:
@@ -0,0 +1,488 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user