From 5e6bff692513e7202c0edcaedf763730636ca49c Mon Sep 17 00:00:00 2001 From: Dan White Date: Wed, 1 Jul 2026 11:44:06 -0600 Subject: [PATCH 1/2] Implement ImportNetworkFile console command for headless builds The console (headless) global command factory had no case for GlobalCommands::ImportNetworkFile, so --import crashed immediately with "Unknown global command type." on any headless build (mac/linux/windows headless CI jobs). This was invisible until PR #2535 wired up ctest in CI, at which point it caused ~90% of unit/regression tests to fail identically (all ImportNetwork.* tests use --import). Add ImportFileCommandConsole, sharing filename resolution/parse/load/ regression-exit logic with LoadFileCommandConsole via a new NetworkFileProcessCommandConsole base class, mirroring the existing GUI FileImportCommand/NetworkFileProcessCommand split. --- src/Core/ConsoleApplication/CMakeLists.txt | 1 + .../ConsoleCommandFactory.cc | 2 + .../ConsoleApplication/ConsoleCommands.cc | 68 ++++++++++++++----- src/Core/ConsoleApplication/ConsoleCommands.h | 34 ++++++++-- 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/src/Core/ConsoleApplication/CMakeLists.txt b/src/Core/ConsoleApplication/CMakeLists.txt index ecc78e9dba..f6a62feaeb 100644 --- a/src/Core/ConsoleApplication/CMakeLists.txt +++ b/src/Core/ConsoleApplication/CMakeLists.txt @@ -47,6 +47,7 @@ SCIRUN_ADD_LIBRARY(Core_ConsoleApplication TARGET_LINK_LIBRARIES(Core_ConsoleApplication Core_Application Core_Command + Core_Serialization_Network_Importer ${SCI_BOOST_LIBRARY} ) diff --git a/src/Core/ConsoleApplication/ConsoleCommandFactory.cc b/src/Core/ConsoleApplication/ConsoleCommandFactory.cc index 6fa935754c..7dcd861e77 100644 --- a/src/Core/ConsoleApplication/ConsoleCommandFactory.cc +++ b/src/Core/ConsoleApplication/ConsoleCommandFactory.cc @@ -54,6 +54,8 @@ CommandHandle ConsoleGlobalCommandFactory::create(GlobalCommands type) const return makeShared(); case GlobalCommands::LoadNetworkFile: return makeShared(); + case GlobalCommands::ImportNetworkFile: + return makeShared(); case GlobalCommands::PrintModules: return makeShared(); case GlobalCommands::SaveNetworkFile: diff --git a/src/Core/ConsoleApplication/ConsoleCommands.cc b/src/Core/ConsoleApplication/ConsoleCommands.cc index 895df66f96..62dcc71028 100644 --- a/src/Core/ConsoleApplication/ConsoleCommands.cc +++ b/src/Core/ConsoleApplication/ConsoleCommands.cc @@ -26,12 +26,14 @@ */ +#include #include #include #include #include #include #include +#include #include #include #include @@ -63,20 +65,19 @@ namespace /// @todo: real logger #define LOG_CONSOLE(x) std::cout << "[SCIRun] " << x << std::endl; -bool LoadFileCommandConsole::execute() +std::string NetworkFileProcessCommandConsole::resolveFilename() const +{ + return get(Variables::Filename).toFilename().string(); +} + +bool NetworkFileProcessCommandConsole::execute() { quietModulesIfNotVerbose(); - auto inputFiles = Application::Instance().parameters()->inputFiles(); - std::string filename; - if (!inputFiles.empty()) - filename = inputFiles[0]; - else - { - filename = get(Variables::Filename).toFilename().string(); - } + auto filename = resolveFilename(); + const auto verb = actionVerb(); - LOG_CONSOLE("Attempting load of " << filename); + LOG_CONSOLE("Attempting " << verb << " of " << filename); if (!boost::filesystem::exists(filename)) { LOG_CONSOLE("File does not exist: " << filename); @@ -84,24 +85,59 @@ bool LoadFileCommandConsole::execute() } try { - auto openedFile = XMLSerializer::load_xml(filename); + auto file = processFile(filename); - if (openedFile) + if (file) { Application::Instance().controller()->clear(); - Application::Instance().controller()->loadNetwork(openedFile); - LOG_CONSOLE("File load done: " << filename); + Application::Instance().controller()->loadNetwork(file); + LOG_CONSOLE("File " << verb << " done: " << filename); return true; } - LOG_CONSOLE("File load failed: " << filename); + LOG_CONSOLE("File " << verb << " failed: " << filename); + } + catch (std::exception& e) + { + LOG_CONSOLE("File " << verb << " failed: " << filename << ", exception: " << e.what()); } catch (...) { - LOG_CONSOLE("File load failed: " << filename); + LOG_CONSOLE("File " << verb << " failed: " << filename); + } + + // Mirrors FileImportCommand/NetworkFileProcessCommand (GUI): a failure that + // matters in regression mode must exit non-zero so ctest reports it, + // instead of silently continuing. + if (failTestOnErrorInRegressionMode() && Application::Instance().parameters()->isRegressionMode()) + { + LOG_CONSOLE("Regression " << verb << " failed, exiting non-zero: " << filename); + std::quick_exit(1); } return false; } +std::string LoadFileCommandConsole::resolveFilename() const +{ + auto inputFiles = Application::Instance().parameters()->inputFiles(); + if (!inputFiles.empty()) + return inputFiles[0]; + return NetworkFileProcessCommandConsole::resolveFilename(); +} + +NetworkFileHandle LoadFileCommandConsole::processFile(const std::string& filename) const +{ + return XMLSerializer::load_xml(filename); +} + +NetworkFileHandle ImportFileCommandConsole::processFile(const std::string& filename) const +{ + auto dtdPath = Core::Application::Instance().executablePath(); + const auto& modFactory = Core::Application::Instance().controller()->moduleFactory(); + std::ostringstream legacyImportLog; + LegacyNetworkIO lnio(dtdPath.string(), modFactory, legacyImportLog); + return lnio.load_net(filename); +} + bool SaveFileCommandConsole::execute() { return !saveImpl(get(Variables::Filename).toFilename().string()).empty(); diff --git a/src/Core/ConsoleApplication/ConsoleCommands.h b/src/Core/ConsoleApplication/ConsoleCommands.h index 8f3743cd72..0ce69cc9c6 100644 --- a/src/Core/ConsoleApplication/ConsoleCommands.h +++ b/src/Core/ConsoleApplication/ConsoleCommands.h @@ -31,19 +31,45 @@ #include #include +#include #include namespace SCIRun { namespace Core { namespace Console { - class SCISHARE LoadFileCommandConsole : public Commands::FileCommand + // Shared logic for the two commands that turn a file on disk into a loaded + // network: resolve a filename, hand it to a format-specific parser, and + // load the result into the controller. Load (.srn5) and Import (legacy + // .srn/.net) differ only in how the filename is resolved, how the file is + // parsed, and whether a parse failure should fail a regression test. + class SCISHARE NetworkFileProcessCommandConsole : public Commands::FileCommand + { + public: + bool execute() final; + protected: + virtual std::string resolveFilename() const; + virtual Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const = 0; + virtual std::string actionVerb() const = 0; + virtual bool failTestOnErrorInRegressionMode() const { return false; } + }; + + class SCISHARE LoadFileCommandConsole : public NetworkFileProcessCommandConsole { public: LoadFileCommandConsole(); - bool execute() override; - // private: - // int index_ = 0; + protected: + std::string resolveFilename() const override; + Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const override; + std::string actionVerb() const override { return "load"; } + }; + + class SCISHARE ImportFileCommandConsole : public NetworkFileProcessCommandConsole + { + protected: + Dataflow::Networks::NetworkFileHandle processFile(const std::string& filename) const override; + std::string actionVerb() const override { return "import"; } + bool failTestOnErrorInRegressionMode() const override { return true; } }; class SCISHARE SaveFileCommandConsole : public Commands::FileCommand, public Commands::SaveFileCommandHelper From fe316899e81443d94410a38ca1e50c4774a609c2 Mon Sep 17 00:00:00 2001 From: Dan White Date: Fri, 3 Jul 2026 22:53:31 -0600 Subject: [PATCH 2/2] Fix macOS-14 CI build: avoid std::quick_exit libc++ using-declaration bug Xcode 15.4 on the mac-headless-14-arm(-slim) runners fails to resolve std::quick_exit via libc++'s using-declaration shim. The global C symbol is unconditionally declared regardless of deployment target, so calling it unqualified sidesteps the toolchain bug. --- src/Core/ConsoleApplication/ConsoleCommands.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Core/ConsoleApplication/ConsoleCommands.cc b/src/Core/ConsoleApplication/ConsoleCommands.cc index 62dcc71028..858e28cee8 100644 --- a/src/Core/ConsoleApplication/ConsoleCommands.cc +++ b/src/Core/ConsoleApplication/ConsoleCommands.cc @@ -111,7 +111,9 @@ bool NetworkFileProcessCommandConsole::execute() if (failTestOnErrorInRegressionMode() && Application::Instance().parameters()->isRegressionMode()) { LOG_CONSOLE("Regression " << verb << " failed, exiting non-zero: " << filename); - std::quick_exit(1); + // std:: qualifier avoided: libc++'s using-declaration for quick_exit fails + // to resolve on Xcode 15.4 (macOS-14 CI runners); the global C symbol works fine. + quick_exit(1); } return false; }