From 9db2d3a13dfdd0f14701efb154e01b5924b5a910 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 12:39:48 +0300 Subject: [PATCH 1/8] CommandWrapper handleDir case is implemented --- src/Compiler/CommandWrapper.h | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Compiler/CommandWrapper.h b/src/Compiler/CommandWrapper.h index 4a5728a7e..2930a3a63 100644 --- a/src/Compiler/CommandWrapper.h +++ b/src/Compiler/CommandWrapper.h @@ -34,6 +34,7 @@ along with this program. If not, see . #include #include +#include // #include // #define DEBUG_ENABLE_PARANOIC_CHECK @@ -514,12 +515,12 @@ class CommandWrapper { void appendFile(const std::string& filePath)=delete; void appendFile(const std::filesystem::path& file, const std::string& mask = "") { appendArgument(file.string(), "", mask); - handleFile(file, mask); + handlePath(file, mask); } void appendFile(const std::string& parameter, const std::string& file)=delete; void appendFile(const std::string& parameter, const std::filesystem::path& file, const std::string& mask = "") { appendArgument(parameter, file.string(), mask); - handleFile(file, mask); + handlePath(file, mask); } void prepend(const std::string& parameter, const std::string& value = "") { @@ -529,13 +530,13 @@ class CommandWrapper { void prependFile(const std::string& file)=delete; void prependFile(const std::filesystem::path& file, const std::string& mask = "") { prependArgument(file.string(), "", mask); - handleFile(file, mask); + handlePath(file, mask); } void prependFile(const std::string& parameter, const std::string& file)=delete; void prependFile(const std::string& parameter, const std::filesystem::path& file, const std::string& mask) { prependArgument(parameter, file.string(), mask); - handleFile(file, mask); + handlePath(file, mask); } private: @@ -605,7 +606,24 @@ class CommandWrapper { } } + void handlePath(const std::filesystem::path& file, const std::string& mask) { + if (std::filesystem::is_directory(file)) { + handeDir(file); + } else { + handleFile(file, mask); + } + } + + void handeDir(const std::filesystem::path& file) { + std::vector files = FileUtils::FindFilesRecursively(file); + for (const auto& file: files) { + handleFile(file); + } + } + void handleFile(const std::filesystem::path& file, const std::string& mask) { + qInfo() << "~~~" << "commandwrapper::handleFile" << file.string().c_str(); + std::filesystem::path resolvedFilePath(file); if (file.is_relative() && !s_projectPath.empty()) { resolvedFilePath = s_projectPath / file; @@ -614,7 +632,7 @@ class CommandWrapper { if (!std::filesystem::exists(resolvedFilePath)) { return; } - + bool skipHashCheck = false; auto it = s_bigFilesSet.find(resolvedFilePath.filename().string()); if (it != s_bigFilesSet.end()) { From 000e1557e023450f45716da5b25f9cbc5a9c2426 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 13:13:19 +0300 Subject: [PATCH 2/8] add FileUtils::FindAbsoluteFilePathsRecursively --- src/Utils/FileUtils.cpp | 19 +++++++++++++++++++ src/Utils/FileUtils.h | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/src/Utils/FileUtils.cpp b/src/Utils/FileUtils.cpp index e33af778d..c74d5466e 100644 --- a/src/Utils/FileUtils.cpp +++ b/src/Utils/FileUtils.cpp @@ -244,6 +244,25 @@ std::filesystem::path candidate = searchPath / filename; return result; } +std::vector FileUtils::FindAbsoluteFilePathsRecursively( + const std::filesystem::path& searchPath) { + std::vector results{}; + if (!FileUtils::FileIsDirectory(searchPath)) { + return results; + } + std::error_code ec; + std::filesystem::recursive_directory_iterator it( + searchPath, std::filesystem::directory_options::skip_permission_denied, + ec); + const std::filesystem::recursive_directory_iterator end; + for (; !ec && it != end; it.increment(ec)) { + if (FileUtils::FileIsRegular(it->path())) { + results.push_back(std::filesystem::absolute(it->path())); + } + } + return results; +} + // This will search the given paths (non-recursively) for a child file. // All matches will be returned in a vector std::vector FileUtils::FindFileInDirs( diff --git a/src/Utils/FileUtils.h b/src/Utils/FileUtils.h index 124f3fd55..728dce09d 100644 --- a/src/Utils/FileUtils.h +++ b/src/Utils/FileUtils.h @@ -69,6 +69,12 @@ class FileUtils final { static std::filesystem::path LocateFileRecursive( const std::filesystem::path& searchPath, const std::string filename); + // Recursively collect every regular file under searchPath as an absolute + // path. Returns an empty vector if searchPath does not exist or is not a + // directory. + static std::vector FindAbsoluteFilePathsRecursively( + const std::filesystem::path& searchPath); + static std::vector FindFileInDirs( const std::string& filename, const std::vector& searchPaths, From d66ea7ebb936c2978c1bec554373a857e08f0041 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 13:38:57 +0300 Subject: [PATCH 3/8] incr compilation logging is enabled by defining env var AURORA_INCR_COMPILATOR_LOG --- src/Compiler/TaskCompilationStateManager.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Compiler/TaskCompilationStateManager.h b/src/Compiler/TaskCompilationStateManager.h index ba0162d5a..08bc2bb0d 100644 --- a/src/Compiler/TaskCompilationStateManager.h +++ b/src/Compiler/TaskCompilationStateManager.h @@ -30,6 +30,7 @@ along with this program. If not, see . #include #include +#include namespace FOEDAG { @@ -37,7 +38,12 @@ constexpr const char* COMPILATION_CACHE_FILENAME = "compilation_cache.json"; class TaskCompilationStateManager { public: TaskCompilationStateManager(Compiler* compiler): m_compiler(compiler) { - + // Read the developer log toggle once and propagate it to CommandWrapper, so + // verbose logging can be enabled at runtime via the environment variable + // without recompiling the app. + static const bool logEnabled = + std::getenv("AURORA_INCR_COMPILATOR_LOG") != nullptr; + CommandWrapper::setLogEnabled(logEnabled); } bool isCompilationRequired(int taskId, const CommandWrapperPtr& command) const { @@ -90,7 +96,7 @@ class TaskCompilationStateManager { } private: - bool m_isLogEnabled = false; + bool m_isLogEnabled = true; Compiler* m_compiler = nullptr; // used for log messages std::unordered_map m_taskCommandsMap; std::filesystem::path m_filePath{COMPILATION_CACHE_FILENAME}; From 2295545710b425c7c8c3b91c408dd8e03d77ee50 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 13:40:17 +0300 Subject: [PATCH 4/8] CommandWrapper now handles dir as well via common handlePath method --- src/Compiler/CommandWrapper.cpp | 1 + src/Compiler/CommandWrapper.h | 58 +++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/Compiler/CommandWrapper.cpp b/src/Compiler/CommandWrapper.cpp index 8940708cd..9fff0b28e 100644 --- a/src/Compiler/CommandWrapper.cpp +++ b/src/Compiler/CommandWrapper.cpp @@ -27,5 +27,6 @@ std::filesystem::path ScriptRenderer::s_projectPath; std::filesystem::path CommandWrapper::s_projectPath; std::unordered_set CommandWrapper::s_bigFilesSet = {}; +bool CommandWrapper::s_enableLog = false; } // namespace FOEDAG diff --git a/src/Compiler/CommandWrapper.h b/src/Compiler/CommandWrapper.h index 2930a3a63..40ccd91dd 100644 --- a/src/Compiler/CommandWrapper.h +++ b/src/Compiler/CommandWrapper.h @@ -464,6 +464,7 @@ CompilationFilesScopedSession& operator=(CompilationFilesScopedSession&&) = dele class CommandWrapper { static std::filesystem::path s_projectPath; static std::unordered_set s_bigFilesSet; + static bool s_enableLog; public: CommandWrapper()=default; @@ -485,6 +486,13 @@ class CommandWrapper { static const std::filesystem::path& projectPath() { return s_projectPath; } static void addBigFileName(const std::string& fileName) { s_bigFilesSet.insert(fileName); } + // Developer toggle for verbose CommandWrapper debug logging. Enabled once at + // startup from the AURORA_INCR_COMPILATOR_LOG environment variable (see + // TaskCompilationStateManager), so debugging can be turned on at runtime + // without recompiling the app. + static void setLogEnabled(bool enabled) { s_enableLog = enabled; } + static bool isLogEnabled() { return s_enableLog; } + DiffCommandPtr collectDiff(const CommandWrapper& old) { DiffCommandPtr diff = std::make_shared(); compareArguments(old.arguments(), arguments(), diff); @@ -606,47 +614,57 @@ class CommandWrapper { } } + void resolveProjectRelativePath(std::filesystem::path& path) { + if (path.is_relative() && !s_projectPath.empty()) { + path = s_projectPath / path; + } + } + void handlePath(const std::filesystem::path& file, const std::string& mask) { - if (std::filesystem::is_directory(file)) { - handeDir(file); + std::filesystem::path resolvedPath(file); + resolveProjectRelativePath(resolvedPath); + + if (!std::filesystem::exists(resolvedPath)) { + return; + } + + if (std::filesystem::is_directory(resolvedPath)) { + handeDir(resolvedPath); } else { - handleFile(file, mask); + handleFile(resolvedPath, mask); } } - void handeDir(const std::filesystem::path& file) { - std::vector files = FileUtils::FindFilesRecursively(file); + void handeDir(const std::filesystem::path& dir) { + if (isLogEnabled()) { + qInfo() << "~~~" << "commandwrapper::handleDir" << dir.string().c_str(); + } + + std::vector files = FileUtils::FindAbsoluteFilePathsRecursively(dir); for (const auto& file: files) { handleFile(file); } } - void handleFile(const std::filesystem::path& file, const std::string& mask) { - qInfo() << "~~~" << "commandwrapper::handleFile" << file.string().c_str(); - - std::filesystem::path resolvedFilePath(file); - if (file.is_relative() && !s_projectPath.empty()) { - resolvedFilePath = s_projectPath / file; + void handleFile(const std::filesystem::path& file, const std::string& mask = "") { + if (isLogEnabled()) { + qInfo() << "~~~" << "commandwrapper::handleFile" << file.string().c_str(); } - if (!std::filesystem::exists(resolvedFilePath)) { - return; - } - bool skipHashCheck = false; - auto it = s_bigFilesSet.find(resolvedFilePath.filename().string()); + auto it = s_bigFilesSet.find(file.filename().string()); if (it != s_bigFilesSet.end()) { skipHashCheck = true; } - if (resolvedFilePath.extension() == ".bin") { + if (file.extension() == ".bin") { skipHashCheck = true; // calc md5 sum for big bin files takes a lot of time } - std::string key = mask.empty()? resolvedFilePath.string(): mask; + std::string key = mask.empty()? file.string(): mask; if (FilesIdentityCache::instance().isEnabled()) { - m_files[key] = FilesIdentityCache::instance().get(resolvedFilePath, mask, skipHashCheck); + m_files[key] = FilesIdentityCache::instance().get(file, mask, skipHashCheck); } else { - m_files[key] = std::make_shared(resolvedFilePath, mask, skipHashCheck); + m_files[key] = std::make_shared(file, mask, skipHashCheck); } } From 090911cd3dfa898f16e8603406fdd9bd00441968 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 18:00:16 +0300 Subject: [PATCH 5/8] rename CommandWrapper::appendFile to CommandWrapper::appendPath --- src/Compiler/CommandWrapper.h | 12 +-- src/Compiler/CompilerOpenFPGA_ql.cpp | 32 ++++---- .../unittest/Compiler/CommandWrapper_test.cpp | 80 +++++++++---------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/Compiler/CommandWrapper.h b/src/Compiler/CommandWrapper.h index 40ccd91dd..f3881706a 100644 --- a/src/Compiler/CommandWrapper.h +++ b/src/Compiler/CommandWrapper.h @@ -520,13 +520,13 @@ class CommandWrapper { appendArgument(parameter, value); } - void appendFile(const std::string& filePath)=delete; - void appendFile(const std::filesystem::path& file, const std::string& mask = "") { + void appendPath(const std::string& filePath)=delete; + void appendPath(const std::filesystem::path& file, const std::string& mask = "") { appendArgument(file.string(), "", mask); handlePath(file, mask); } - void appendFile(const std::string& parameter, const std::string& file)=delete; - void appendFile(const std::string& parameter, const std::filesystem::path& file, const std::string& mask = "") { + void appendPath(const std::string& parameter, const std::string& file)=delete; + void appendPath(const std::string& parameter, const std::filesystem::path& file, const std::string& mask = "") { appendArgument(parameter, file.string(), mask); handlePath(file, mask); } @@ -637,7 +637,7 @@ class CommandWrapper { void handeDir(const std::filesystem::path& dir) { if (isLogEnabled()) { - qInfo() << "~~~" << "commandwrapper::handleDir" << dir.string().c_str(); + qInfo() << "commandwrapper::handleDir" << dir.string().c_str(); } std::vector files = FileUtils::FindAbsoluteFilePathsRecursively(dir); @@ -648,7 +648,7 @@ class CommandWrapper { void handleFile(const std::filesystem::path& file, const std::string& mask = "") { if (isLogEnabled()) { - qInfo() << "~~~" << "commandwrapper::handleFile" << file.string().c_str(); + qInfo() << "commandwrapper::handleFile" << file.string().c_str(); } bool skipHashCheck = false; diff --git a/src/Compiler/CompilerOpenFPGA_ql.cpp b/src/Compiler/CompilerOpenFPGA_ql.cpp index 307034782..2e3fb0cfa 100644 --- a/src/Compiler/CompilerOpenFPGA_ql.cpp +++ b/src/Compiler/CompilerOpenFPGA_ql.cpp @@ -2567,9 +2567,9 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ QLSettingsManager::getStringValue("vpr", "filename", "net_file") + " , specified in vpr>filename>net_file setting. \n"); return nullptr; } - command->appendFile("--net_file", QLSettingsManager::getPathValue("vpr", "filename", "net_file")); + command->appendPath("--net_file", QLSettingsManager::getPathValue("vpr", "filename", "net_file")); } else { - command->appendFile("--net_file", std::filesystem::path{netlistFilePrefix + std::string(".net")}); + command->appendPath("--net_file", std::filesystem::path{netlistFilePrefix + std::string(".net")}); } if (cfg.use_place_file) { @@ -2579,9 +2579,9 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ QLSettingsManager::getStringValue("vpr", "filename", "place_file") + " , specified in vpr>filename>place_file setting. \n"); return nullptr; } - command->appendFile("--place_file", QLSettingsManager::getPathValue("vpr", "filename", "place_file")); + command->appendPath("--place_file", QLSettingsManager::getPathValue("vpr", "filename", "place_file")); } else { - command->appendFile("--place_file", std::filesystem::path{netlistFilePrefix + std::string(".place")}); + command->appendPath("--place_file", std::filesystem::path{netlistFilePrefix + std::string(".place")}); } } @@ -2592,9 +2592,9 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ QLSettingsManager::getStringValue("vpr", "filename", "route_file") + " , specified in vpr>filename>route_file setting. \n"); return nullptr; } - command->appendFile("--route_file", QLSettingsManager::getPathValue("vpr", "filename", "route_file")); + command->appendPath("--route_file", QLSettingsManager::getPathValue("vpr", "filename", "route_file")); } else { - command->appendFile("--route_file", std::filesystem::path{netlistFilePrefix + std::string(".route")}); + command->appendPath("--route_file", std::filesystem::path{netlistFilePrefix + std::string(".route")}); } } @@ -2611,7 +2611,7 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ // if we have a valid sdc_file_path at this point, pass it on to vpr: if(!sdc_file_path.empty()) { Message(std::string("SDC file found: ") + sdc_file_path.string()); - command->appendFile("--sdc_file", sdc_file_path); + command->appendPath("--sdc_file", sdc_file_path); } else { Message(std::string("SDC file not found, no constraints passed to vpr.")); @@ -2620,7 +2620,7 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ if( !QLSettingsManager::getStringValue("vpr", "filename", "write_rr_graph").empty() ) { - command->appendFile("--write_rr_graph", QLSettingsManager::getPathValue("vpr", "filename", "write_rr_graph")); + command->appendPath("--write_rr_graph", QLSettingsManager::getPathValue("vpr", "filename", "write_rr_graph")); } // parse vpr netlist options @@ -2750,8 +2750,8 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ QLDeviceManager::getInstance()->deviceVPRRouterLookaheadFile(device_target); if(!rr_graph_file_path.empty() && !router_lookahead_file_path.empty()) { - command->appendFile("--read_rr_graph", rr_graph_file_path); - command->appendFile("--read_router_lookahead", router_lookahead_file_path); + command->appendPath("--read_rr_graph", rr_graph_file_path); + command->appendPath("--read_router_lookahead", router_lookahead_file_path); } else { // no rr_graph available to use, try to use dynamic rr_graph generation. @@ -2766,8 +2766,8 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ // Plaintext and encrypted CRR flows pass --sb_maps/--sb_templates // identically; encryption only adds --crypt_key_db. - command->appendFile("--sb_maps", m_SBMapsFile); - command->appendFile("--sb_templates", m_SBTemplatesDir); + command->appendPath("--sb_maps", m_SBMapsFile); + command->appendPath("--sb_templates", m_SBTemplatesDir); // VPR keys CRR encryption solely off the ".en" suffix on --sb_maps and // then requires --crypt_key_db (see VTR read_options.cpp). Keep FOEDAG in @@ -2787,7 +2787,7 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ ErrorMessage("Cannot find key database for encrypted device data: " + crypt_key_db.string()); return nullptr; } - command->appendFile("--crypt_key_db", crypt_key_db); + command->appendPath("--crypt_key_db", crypt_key_db); } command->append("--preserve_input_pin_connections off"); @@ -2841,7 +2841,7 @@ CommandWrapperPtr CompilerOpenFPGA_ql::BaseVprCommand(QLDeviceTarget device_targ std::filesystem::path fp_constraint_filepath = ProjManager()->projectName() + "_constraints.xml"; std::filesystem::path fp_constraint_filepath_absolute = std::filesystem::path(ProjManager()->projectPath()) / fp_constraint_filepath; if (fs::exists(fp_constraint_filepath_absolute)) { - command->appendFile("--read_vpr_constraints", std::filesystem::path{ProjManager()->projectName() + "_constraints.xml"}); + command->appendPath("--read_vpr_constraints", std::filesystem::path{ProjManager()->projectName() + "_constraints.xml"}); } } else { //IO floorplanning generation failed, must stop the flow @@ -4757,7 +4757,7 @@ bool CompilerOpenFPGA_ql::TimingAnalysisHelper(const QLDeviceTarget& current_dev std::filesystem::is_regular_file(sdfFileName) && std::filesystem::is_regular_file(sdcFileName)) { taCommand = BaseStaCommand(); - taCommand->appendFile(BaseStaScript(libFileName, netlistFileName, sdfFileName, sdcFileName)); + taCommand->appendPath(BaseStaScript(libFileName, netlistFileName, sdfFileName, sdcFileName)); FileUtils::WriteToFile(sta_cmd_filepath, taCommand->string()); } else { @@ -9975,7 +9975,7 @@ CommandWrapperPtr CompilerOpenFPGA_ql::getPlacementCommand() { // if (!filepath_fpga_fix_pins_place_str.empty()) { - // command->appendFile("--fix_clusters", std::filesystem::path(filepath_fpga_fix_pins_place_str)); + // command->appendPath("--fix_clusters", std::filesystem::path(filepath_fpga_fix_pins_place_str)); // } // else // { diff --git a/tests/unittest/Compiler/CommandWrapper_test.cpp b/tests/unittest/Compiler/CommandWrapper_test.cpp index 5608f227c..caa096113 100644 --- a/tests/unittest/Compiler/CommandWrapper_test.cpp +++ b/tests/unittest/Compiler/CommandWrapper_test.cpp @@ -71,10 +71,10 @@ TEST(CommandWrapper, to_string) command.append("--p1", "v1"); command.append("--p2"); command.append("--p3", "v3"); - command.appendFile("--file1", filePath1.filePath()); - command.appendFile(filePath2.filePath()); - command.appendFile(filePath3.filePath(), "arch"); - command.appendFile("--file4", filePath4.filePath(), "arch"); + command.appendPath("--file1", filePath1.filePath()); + command.appendPath(filePath2.filePath()); + command.appendPath(filePath3.filePath(), "arch"); + command.appendPath("--file4", filePath4.filePath(), "arch"); EXPECT_EQ("cmd --p1 v1 --p2 --p3 v3 --file1 filepath1 filepath2 filepath3 --file4 filepath4", command.string()); } @@ -88,15 +88,15 @@ TEST(CommandWrapper, compare_matches) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(diff->isEmpty()); @@ -111,14 +111,14 @@ TEST(CommandWrapper, remove_argument) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -142,14 +142,14 @@ TEST(CommandWrapper, remove_argument_file) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile(filePath2.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -172,16 +172,16 @@ TEST(CommandWrapper, add_argument) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); command2.append("--p4", "v4"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -204,14 +204,14 @@ TEST(CommandWrapper, add_argument_file) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); + command1.appendPath("--file1", filePath1.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -234,15 +234,15 @@ TEST(CommandWrapper, change_argument) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3_NEW"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -266,15 +266,15 @@ TEST(CommandWrapper, change_argument_file) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile("--file1", std::filesystem::path{"filepath1_NEW"}); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", std::filesystem::path{"filepath1_NEW"}); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -300,8 +300,8 @@ TEST(CommandWrapper, file_content_changed) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile("--file1", filePath1.filePath()); - command1.appendFile(filePath2.filePath()); + command1.appendPath("--file1", filePath1.filePath()); + command1.appendPath(filePath2.filePath()); filePath1.appendContent("0101...", 2); const std::string filePath1ModificationTimeStr = FileUtils::ModifiedTimeStr(filePath1.filePath()); @@ -310,8 +310,8 @@ TEST(CommandWrapper, file_content_changed) command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile("--file1", filePath1.filePath()); - command2.appendFile(filePath2.filePath()); + command2.appendPath("--file1", filePath1.filePath()); + command2.appendPath(filePath2.filePath()); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -338,13 +338,13 @@ TEST(CommandWrapper, masked_files_matches) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile(filePath1.filePath(), "arch"); + command1.appendPath(filePath1.filePath(), "arch"); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile(filePath2.filePath(), "arch"); + command2.appendPath(filePath2.filePath(), "arch"); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(diff->isEmpty()); @@ -365,13 +365,13 @@ TEST(CommandWrapper, masked_files_differ) command1.append("--p1", "v1"); command1.append("--p2"); command1.append("--p3", "v3"); - command1.appendFile(filePath1.filePath(), "arch"); + command1.appendPath(filePath1.filePath(), "arch"); CommandWrapper command2{"cmd"}; command2.append("--p1", "v1"); command2.append("--p2"); command2.append("--p3", "v3"); - command2.appendFile(filePath2.filePath(), "arch"); + command2.appendPath(filePath2.filePath(), "arch"); DiffCommandPtr diff = command2.collectDiff(command1); EXPECT_TRUE(!diff->isEmpty()); @@ -396,8 +396,8 @@ TEST(CommandWrapper, serialization) commandOrig.append("--p1", "v1"); commandOrig.append("--p2"); commandOrig.append("--p3", "v3"); - commandOrig.appendFile(filePath1.filePath(), "arch"); - commandOrig.appendFile("--file2", filePath2.filePath()); + commandOrig.appendPath(filePath1.filePath(), "arch"); + commandOrig.appendPath("--file2", filePath2.filePath()); ScriptRendererPtr script = std::make_shared("www"); commandOrig.setScriptRenderer(script); From cff5ce19d062bad4c1f9a7d7b0c2499ffdbb3743 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 18:01:20 +0300 Subject: [PATCH 6/8] TaskCompilationStateManager_test.cpp --- tests/unittest/CMakeLists.txt | 1 + .../TaskCompilationStateManager_test.cpp | 303 ++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 tests/unittest/Compiler/TaskCompilationStateManager_test.cpp diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index be6b7b0e1..011d57694 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -49,6 +49,7 @@ set (CPP_LIST Compiler/CompilerDefines_test.cpp Compiler/BlifParser_test.cpp Compiler/CommandWrapper_test.cpp + Compiler/TaskCompilationStateManager_test.cpp PinAssignment/PortsModel_test.cpp PinAssignment/PinAssignmentBaseView_test.cpp Simulation/Simulation_test.cpp diff --git a/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp b/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp new file mode 100644 index 000000000..27cd3d6b6 --- /dev/null +++ b/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp @@ -0,0 +1,303 @@ +/* +Copyright 2022 The Foedag team + +GPL License + +Copyright (c) 2022 The Open-Source FPGA Foundation + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "Compiler/TaskCompilationStateManager.h" + +#include "Compiler/CommandWrapper.h" +#include "Compiler/Compiler.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace FOEDAG; + +namespace { + +// RAII helper that materializes a file with the given content and removes it on +// destruction, so each test leaves the filesystem clean. +class ScopedFile { +public: + ScopedFile(const std::filesystem::path& filePath, const std::string& content) + : m_filePath(filePath), m_content(content) { + FileUtils::WriteToFile(m_filePath, m_content); + } + + const std::filesystem::path& filePath() const { return m_filePath; } + + void appendContent(const std::string& appendix) { + m_content += appendix; + FileUtils::WriteToFile(m_filePath, m_content); + } + + ~ScopedFile() { FileUtils::removeFile(m_filePath); } + +private: + std::filesystem::path m_filePath; + std::string m_content; +}; + +// Compiler whose logging is silenced so the manager's diagnostic messages do +// not pollute the test output. Message()/ErrorMessage() are virtual, so a thin +// override is enough and we avoid depending on a real Tcl/output setup. +class SilentCompiler : public Compiler { +public: + void Message(const std::string& /*message*/) const override {} + void ErrorMessage(const std::string& /*message*/, + bool /*append*/ = true) const override {} +}; + +class TaskCompilationStateManagerTest : public ::testing::Test { +protected: + void SetUp() override { + const std::string testName = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + m_projectDir = std::filesystem::temp_directory_path() / + ("tcsm_test_" + testName); + std::filesystem::remove_all(m_projectDir); + std::filesystem::create_directories(m_projectDir); + } + + void TearDown() override { + std::filesystem::remove_all(m_projectDir); + // The project path lives in static state shared across the whole test + // binary, so reset it to avoid leaking into unrelated tests. + CommandWrapper::setProjectPath({}); + ScriptRenderer::setProjectPath({}); + } + + std::filesystem::path inProject(const std::string& name) const { + return m_projectDir / name; + } + + // Builds a representative task command: a couple of plain arguments plus two + // source files referenced by absolute path. + CommandWrapperPtr makeCommand(const std::filesystem::path& src1, + const std::filesystem::path& src2, + const std::string& effort = "high") { + auto cmd = std::make_shared("synth"); + cmd->append("--top", "top_module"); + cmd->append("--effort", effort); + cmd->appendPath("--src1", src1); + cmd->appendPath("--src2", src2); + return cmd; + } + + SilentCompiler m_compiler; + std::filesystem::path m_projectDir; +}; + +} // namespace + +// A task that has never been stored must always be (re)compiled. +TEST_F(TaskCompilationStateManagerTest, never_compiled_task_requires_compilation) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + EXPECT_TRUE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); +} + +// Storing a task and re-checking it with an identical command (files untouched) +// must report the task as up-to-date. +TEST_F(TaskCompilationStateManagerTest, unchanged_task_does_not_require_recompilation) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + EXPECT_FALSE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); +} + +// Editing the content of an input file must mark the task dirty. +TEST_F(TaskCompilationStateManagerTest, changed_file_content_marks_task_dirty) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + src1.appendContent("\n// touched"); + + EXPECT_TRUE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); +} + +// Changing a command-line argument value must mark the task dirty even if every +// input file is byte-for-byte identical. +TEST_F(TaskCompilationStateManagerTest, changed_argument_marks_task_dirty) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath(), "high")); + + EXPECT_TRUE(mgr.isCompilationRequired( + 1, makeCommand(src1.filePath(), src2.filePath(), "low"))); +} + +// Adding a new input file to the task must mark it dirty. +TEST_F(TaskCompilationStateManagerTest, added_file_marks_task_dirty) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + ScopedFile src3{inProject("c.v"), "module c; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + auto extended = makeCommand(src1.filePath(), src2.filePath()); + extended->appendPath("--src3", src3.filePath()); + + EXPECT_TRUE(mgr.isCompilationRequired(1, extended)); +} + +// Removing an input file from the task must mark it dirty. +TEST_F(TaskCompilationStateManagerTest, removed_file_marks_task_dirty) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + auto reduced = std::make_shared("synth"); + reduced->append("--top", "top_module"); + reduced->append("--effort", "high"); + reduced->appendPath("--src1", src1.filePath()); + + EXPECT_TRUE(mgr.isCompilationRequired(1, reduced)); +} + +// Different task ids are tracked independently: storing task 1 must not make +// task 2 look up-to-date. +TEST_F(TaskCompilationStateManagerTest, tasks_are_tracked_independently) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + EXPECT_FALSE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); + EXPECT_TRUE(mgr.isCompilationRequired(2, makeCommand(src1.filePath(), src2.filePath()))); +} + +// The same task id under a different profile is a distinct entry. +TEST_F(TaskCompilationStateManagerTest, profiles_are_tracked_independently) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + mgr.storeTaskCommand(1, "rtl", makeCommand(src1.filePath(), src2.filePath())); + + EXPECT_FALSE(mgr.isCompilationRequired( + 1, "rtl", makeCommand(src1.filePath(), src2.filePath()))); + EXPECT_TRUE(mgr.isCompilationRequired( + 1, "timing", makeCommand(src1.filePath(), src2.filePath()))); +} + +// State persisted to compilation_cache.json must survive being reloaded by a +// fresh manager instance. +TEST_F(TaskCompilationStateManagerTest, state_survives_persist_and_reload) { + ScopedFile src1{inProject("a.v"), "module a; endmodule"}; + ScopedFile src2{inProject("b.v"), "module b; endmodule"}; + + { + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + } + + TaskCompilationStateManager reloaded(&m_compiler); + reloaded.setProjectPath(m_projectDir); + reloaded.load(); + + EXPECT_FALSE(reloaded.isCompilationRequired( + 1, makeCommand(src1.filePath(), src2.filePath()))); + + src1.appendContent("\n// touched after reload"); + EXPECT_TRUE(reloaded.isCompilationRequired( + 1, makeCommand(src1.filePath(), src2.filePath()))); +} + +// A whole directory passed as a task input is expanded recursively; changing a +// file inside that directory must mark the task dirty (exercises the directory +// expansion path in CommandWrapper). +TEST_F(TaskCompilationStateManagerTest, directory_input_detects_inner_file_change) { + const std::filesystem::path srcDir = inProject("rtl"); + std::filesystem::create_directories(srcDir / "sub"); + ScopedFile inner1{srcDir / "x.v", "module x; endmodule"}; + ScopedFile inner2{srcDir / "sub" / "y.v", "module y; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + auto makeDirCommand = [&]() { + auto cmd = std::make_shared("synth"); + cmd->append("--top", "top_module"); + cmd->appendPath("--srcdir", srcDir); + return cmd; + }; + + mgr.storeTaskCommand(1, makeDirCommand()); + + // Unchanged directory -> up-to-date. + EXPECT_FALSE(mgr.isCompilationRequired(1, makeDirCommand())); + + // Edit a file nested inside the directory -> dirty. + inner2.appendContent("\n// touched"); + EXPECT_TRUE(mgr.isCompilationRequired(1, makeDirCommand())); +} + +// Adding a new file into a directory input must also mark the task dirty. +TEST_F(TaskCompilationStateManagerTest, directory_input_detects_new_file) { + const std::filesystem::path srcDir = inProject("rtl"); + std::filesystem::create_directories(srcDir); + ScopedFile inner1{srcDir / "x.v", "module x; endmodule"}; + + TaskCompilationStateManager mgr(&m_compiler); + mgr.setProjectPath(m_projectDir); + + auto makeDirCommand = [&]() { + auto cmd = std::make_shared("synth"); + cmd->appendPath("--srcdir", srcDir); + return cmd; + }; + + mgr.storeTaskCommand(1, makeDirCommand()); + + ScopedFile inner2{srcDir / "z.v", "module z; endmodule"}; + EXPECT_TRUE(mgr.isCompilationRequired(1, makeDirCommand())); +} From 93298f58e1224a538a5d4b5fa5f161d9688a67c2 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 18:27:18 +0300 Subject: [PATCH 7/8] inline COmmandWrapper build in unittest --- .../TaskCompilationStateManager_test.cpp | 225 ++++++++++++------ 1 file changed, 157 insertions(+), 68 deletions(-) diff --git a/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp b/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp index 27cd3d6b6..d1e3d1d78 100644 --- a/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp +++ b/tests/unittest/Compiler/TaskCompilationStateManager_test.cpp @@ -88,19 +88,6 @@ class TaskCompilationStateManagerTest : public ::testing::Test { return m_projectDir / name; } - // Builds a representative task command: a couple of plain arguments plus two - // source files referenced by absolute path. - CommandWrapperPtr makeCommand(const std::filesystem::path& src1, - const std::filesystem::path& src2, - const std::string& effort = "high") { - auto cmd = std::make_shared("synth"); - cmd->append("--top", "top_module"); - cmd->append("--effort", effort); - cmd->appendPath("--src1", src1); - cmd->appendPath("--src2", src2); - return cmd; - } - SilentCompiler m_compiler; std::filesystem::path m_projectDir; }; @@ -115,7 +102,13 @@ TEST_F(TaskCompilationStateManagerTest, never_compiled_task_requires_compilation TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - EXPECT_TRUE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); + auto command = std::make_shared("synth"); + command->append("--top", "top_module"); + command->append("--effort", "high"); + command->appendPath("--src1", src1.filePath()); + command->appendPath("--src2", src2.filePath()); + + EXPECT_TRUE(mgr.isCompilationRequired(1, command)); } // Storing a task and re-checking it with an identical command (files untouched) @@ -127,9 +120,20 @@ TEST_F(TaskCompilationStateManagerTest, unchanged_task_does_not_require_recompil TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); + + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "high"); + recheck->appendPath("--src1", src1.filePath()); + recheck->appendPath("--src2", src2.filePath()); - EXPECT_FALSE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); + EXPECT_FALSE(mgr.isCompilationRequired(1, recheck)); } // Editing the content of an input file must mark the task dirty. @@ -140,11 +144,22 @@ TEST_F(TaskCompilationStateManagerTest, changed_file_content_marks_task_dirty) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); src1.appendContent("\n// touched"); - EXPECT_TRUE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "high"); + recheck->appendPath("--src1", src1.filePath()); + recheck->appendPath("--src2", src2.filePath()); + + EXPECT_TRUE(mgr.isCompilationRequired(1, recheck)); } // Changing a command-line argument value must mark the task dirty even if every @@ -156,10 +171,20 @@ TEST_F(TaskCompilationStateManagerTest, changed_argument_marks_task_dirty) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath(), "high")); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); + + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "low"); // changed argument value + recheck->appendPath("--src1", src1.filePath()); + recheck->appendPath("--src2", src2.filePath()); - EXPECT_TRUE(mgr.isCompilationRequired( - 1, makeCommand(src1.filePath(), src2.filePath(), "low"))); + EXPECT_TRUE(mgr.isCompilationRequired(1, recheck)); } // Adding a new input file to the task must mark it dirty. @@ -171,12 +196,21 @@ TEST_F(TaskCompilationStateManagerTest, added_file_marks_task_dirty) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); - - auto extended = makeCommand(src1.filePath(), src2.filePath()); - extended->appendPath("--src3", src3.filePath()); - - EXPECT_TRUE(mgr.isCompilationRequired(1, extended)); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); + + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "high"); + recheck->appendPath("--src1", src1.filePath()); + recheck->appendPath("--src2", src2.filePath()); + recheck->appendPath("--src3", src3.filePath()); // extra input file + + EXPECT_TRUE(mgr.isCompilationRequired(1, recheck)); } // Removing an input file from the task must mark it dirty. @@ -187,14 +221,19 @@ TEST_F(TaskCompilationStateManagerTest, removed_file_marks_task_dirty) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); - auto reduced = std::make_shared("synth"); - reduced->append("--top", "top_module"); - reduced->append("--effort", "high"); - reduced->appendPath("--src1", src1.filePath()); + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "high"); + recheck->appendPath("--src1", src1.filePath()); // --src2 dropped - EXPECT_TRUE(mgr.isCompilationRequired(1, reduced)); + EXPECT_TRUE(mgr.isCompilationRequired(1, recheck)); } // Different task ids are tracked independently: storing task 1 must not make @@ -206,10 +245,26 @@ TEST_F(TaskCompilationStateManagerTest, tasks_are_tracked_independently) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); - - EXPECT_FALSE(mgr.isCompilationRequired(1, makeCommand(src1.filePath(), src2.filePath()))); - EXPECT_TRUE(mgr.isCompilationRequired(2, makeCommand(src1.filePath(), src2.filePath()))); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); + + auto task1Recheck = std::make_shared("synth"); + task1Recheck->append("--top", "top_module"); + task1Recheck->append("--effort", "high"); + task1Recheck->appendPath("--src1", src1.filePath()); + task1Recheck->appendPath("--src2", src2.filePath()); + EXPECT_FALSE(mgr.isCompilationRequired(1, task1Recheck)); + + auto task2Command = std::make_shared("synth"); + task2Command->append("--top", "top_module"); + task2Command->append("--effort", "high"); + task2Command->appendPath("--src1", src1.filePath()); + task2Command->appendPath("--src2", src2.filePath()); + EXPECT_TRUE(mgr.isCompilationRequired(2, task2Command)); } // The same task id under a different profile is a distinct entry. @@ -220,12 +275,26 @@ TEST_F(TaskCompilationStateManagerTest, profiles_are_tracked_independently) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, "rtl", makeCommand(src1.filePath(), src2.filePath())); - - EXPECT_FALSE(mgr.isCompilationRequired( - 1, "rtl", makeCommand(src1.filePath(), src2.filePath()))); - EXPECT_TRUE(mgr.isCompilationRequired( - 1, "timing", makeCommand(src1.filePath(), src2.filePath()))); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, "rtl", stored); + + auto sameProfile = std::make_shared("synth"); + sameProfile->append("--top", "top_module"); + sameProfile->append("--effort", "high"); + sameProfile->appendPath("--src1", src1.filePath()); + sameProfile->appendPath("--src2", src2.filePath()); + EXPECT_FALSE(mgr.isCompilationRequired(1, "rtl", sameProfile)); + + auto otherProfile = std::make_shared("synth"); + otherProfile->append("--top", "top_module"); + otherProfile->append("--effort", "high"); + otherProfile->appendPath("--src1", src1.filePath()); + otherProfile->appendPath("--src2", src2.filePath()); + EXPECT_TRUE(mgr.isCompilationRequired(1, "timing", otherProfile)); } // State persisted to compilation_cache.json must survive being reloaded by a @@ -237,24 +306,40 @@ TEST_F(TaskCompilationStateManagerTest, state_survives_persist_and_reload) { { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - mgr.storeTaskCommand(1, makeCommand(src1.filePath(), src2.filePath())); + + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->append("--effort", "high"); + stored->appendPath("--src1", src1.filePath()); + stored->appendPath("--src2", src2.filePath()); + mgr.storeTaskCommand(1, stored); } TaskCompilationStateManager reloaded(&m_compiler); reloaded.setProjectPath(m_projectDir); reloaded.load(); - EXPECT_FALSE(reloaded.isCompilationRequired( - 1, makeCommand(src1.filePath(), src2.filePath()))); + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->append("--effort", "high"); + recheck->appendPath("--src1", src1.filePath()); + recheck->appendPath("--src2", src2.filePath()); + EXPECT_FALSE(reloaded.isCompilationRequired(1, recheck)); src1.appendContent("\n// touched after reload"); - EXPECT_TRUE(reloaded.isCompilationRequired( - 1, makeCommand(src1.filePath(), src2.filePath()))); + + auto afterEdit = std::make_shared("synth"); + afterEdit->append("--top", "top_module"); + afterEdit->append("--effort", "high"); + afterEdit->appendPath("--src1", src1.filePath()); + afterEdit->appendPath("--src2", src2.filePath()); + EXPECT_TRUE(reloaded.isCompilationRequired(1, afterEdit)); } // A whole directory passed as a task input is expanded recursively; changing a // file inside that directory must mark the task dirty (exercises the directory -// expansion path in CommandWrapper). +// expansion path in CommandWrapper). Only the folder is added via appendPath - +// never the individual files. TEST_F(TaskCompilationStateManagerTest, directory_input_detects_inner_file_change) { const std::filesystem::path srcDir = inProject("rtl"); std::filesystem::create_directories(srcDir / "sub"); @@ -264,24 +349,28 @@ TEST_F(TaskCompilationStateManagerTest, directory_input_detects_inner_file_chang TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - auto makeDirCommand = [&]() { - auto cmd = std::make_shared("synth"); - cmd->append("--top", "top_module"); - cmd->appendPath("--srcdir", srcDir); - return cmd; - }; - - mgr.storeTaskCommand(1, makeDirCommand()); + auto stored = std::make_shared("synth"); + stored->append("--top", "top_module"); + stored->appendPath("--srcdir", srcDir); + mgr.storeTaskCommand(1, stored); // Unchanged directory -> up-to-date. - EXPECT_FALSE(mgr.isCompilationRequired(1, makeDirCommand())); + auto recheck = std::make_shared("synth"); + recheck->append("--top", "top_module"); + recheck->appendPath("--srcdir", srcDir); + EXPECT_FALSE(mgr.isCompilationRequired(1, recheck)); // Edit a file nested inside the directory -> dirty. inner2.appendContent("\n// touched"); - EXPECT_TRUE(mgr.isCompilationRequired(1, makeDirCommand())); + + auto afterEdit = std::make_shared("synth"); + afterEdit->append("--top", "top_module"); + afterEdit->appendPath("--srcdir", srcDir); + EXPECT_TRUE(mgr.isCompilationRequired(1, afterEdit)); } -// Adding a new file into a directory input must also mark the task dirty. +// Adding a new file into a directory input must also mark the task dirty. Only +// the folder is added via appendPath - never the individual files. TEST_F(TaskCompilationStateManagerTest, directory_input_detects_new_file) { const std::filesystem::path srcDir = inProject("rtl"); std::filesystem::create_directories(srcDir); @@ -290,14 +379,14 @@ TEST_F(TaskCompilationStateManagerTest, directory_input_detects_new_file) { TaskCompilationStateManager mgr(&m_compiler); mgr.setProjectPath(m_projectDir); - auto makeDirCommand = [&]() { - auto cmd = std::make_shared("synth"); - cmd->appendPath("--srcdir", srcDir); - return cmd; - }; - - mgr.storeTaskCommand(1, makeDirCommand()); + auto stored = std::make_shared("synth"); + stored->appendPath("--srcdir", srcDir); + mgr.storeTaskCommand(1, stored); + // Drop a new file into the directory after storing. ScopedFile inner2{srcDir / "z.v", "module z; endmodule"}; - EXPECT_TRUE(mgr.isCompilationRequired(1, makeDirCommand())); + + auto recheck = std::make_shared("synth"); + recheck->appendPath("--srcdir", srcDir); + EXPECT_TRUE(mgr.isCompilationRequired(1, recheck)); } From ecd4d0f266f408a7919868b7415375e6fb481599 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Sun, 28 Jun 2026 21:33:17 +0300 Subject: [PATCH 8/8] unify debug log message --- src/Compiler/TaskCompilationStateManager.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Compiler/TaskCompilationStateManager.h b/src/Compiler/TaskCompilationStateManager.h index 08bc2bb0d..9247cb67a 100644 --- a/src/Compiler/TaskCompilationStateManager.h +++ b/src/Compiler/TaskCompilationStateManager.h @@ -96,7 +96,6 @@ class TaskCompilationStateManager { } private: - bool m_isLogEnabled = true; Compiler* m_compiler = nullptr; // used for log messages std::unordered_map m_taskCommandsMap; std::filesystem::path m_filePath{COMPILATION_CACHE_FILENAME}; @@ -123,7 +122,7 @@ class TaskCompilationStateManager { auto it = m_taskCommandsMap.find(id); if (it == m_taskCommandsMap.end()) { - if (m_isLogEnabled) { + if (CommandWrapper::isLogEnabled()) { m_compiler->Message(logPrefix() + "task [" + id + "] wasn't previously compiled"); } return true; @@ -131,7 +130,7 @@ class TaskCompilationStateManager { const CommandWrapper& commandOld = *it->second; DiffCommandPtr diff = command->collectDiff(commandOld); if (!diff->isEmpty()) { - if (m_isLogEnabled) { + if (CommandWrapper::isLogEnabled()) { for (const std::string& msg: diff->messages()) { m_compiler->Message(logPrefix() + "task(" + id + "), detects " + msg); }