From 7caaf4abca596bd6ed296310fa0cca8185c0fbaa Mon Sep 17 00:00:00 2001 From: Anders Kvellestad Date: Tue, 5 May 2026 20:26:53 +0200 Subject: [PATCH 1/2] Added fast track in core.cpp for arguments "--help", "-h" and "--version". --- Core/src/core.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Core/src/core.cpp b/Core/src/core.cpp index ac022b1aaf..fbc1e01e28 100644 --- a/Core/src/core.cpp +++ b/Core/src/core.cpp @@ -635,6 +635,18 @@ namespace Gambit } } + // Fast track for the options "--help", "-h", "--version" or no arguments + // at all. These all end in process_primary_options bailing out with usage + // or version text via SilentShutdownException, so we can skip the database + // build (check_databases) and the scanner-plugin enumeration done further down. + if (command == "--help" || command == "-h" || command == "--version" || command == "none") + { + if (not processed_options) (void) process_primary_options(argc, argv); + // process_primary_options is expected to bail() / throw for these + // inputs. If for some reason it returns, fall through to the existing + // logic below. + } + // Initial list of valid diagnostic commands std::vector valid_commands = initVector("modules", "backends", "models", "capabilities", "scanners", "test-functions"); From 469bb2537a580b67d1437de866455515d963fad1 Mon Sep 17 00:00:00 2001 From: Anders Kvellestad Date: Wed, 6 May 2026 16:15:39 +0200 Subject: [PATCH 2/2] Preload lib: take care of --help / --version / no-args before main binary loads. By letting the gambit_preload library identify trivial gambit invocations like "gambit --help" and "gambit --version" and produce the correct CLI help text, we avoid having to wait for the full GAMBIT initialisation for these simple cases. (The GAMBIT initialisation typically takes a few seconds.) --- Core/include/gambit/Core/cli_help_text.hpp | 65 +++++++++++++++++ Core/src/core.cpp | 46 +----------- cmake/contrib.cmake | 2 +- contrib/preload/gambit_preload.cpp | 81 ++++++++++++++++++++++ 4 files changed, 149 insertions(+), 45 deletions(-) create mode 100644 Core/include/gambit/Core/cli_help_text.hpp diff --git a/Core/include/gambit/Core/cli_help_text.hpp b/Core/include/gambit/Core/cli_help_text.hpp new file mode 100644 index 0000000000..6226f09f8c --- /dev/null +++ b/Core/include/gambit/Core/cli_help_text.hpp @@ -0,0 +1,65 @@ +// GAMBIT: Global and Modular BSM Inference Tool +// ********************************************* +/// \file +/// +/// Top-level CLI usage text for the gambit executable. +/// +/// Defined once here and consumed both by Gambit::gambit_core::bail() +/// in Core/src/core.cpp and by the preload library's +/// __attribute__((constructor)) in contrib/preload/gambit_preload.cpp, +/// which intercepts trivial flag-only invocations (--help, -h, +/// --version, no args) and exits before the main binary's C++ static +/// initialisers run. +/// +/// ********************************************* + +#ifndef __gambit_cli_help_text_hpp__ +#define __gambit_cli_help_text_hpp__ + +namespace Gambit +{ + + inline constexpr const char* cli_help_text = + "\nusage: gambit [options] [] " + "\n " + "\nRun scan: " + "\n gambit -f Start a scan using instructions from inifile " + "\n e.g.: gambit -f gambit.yaml " + "\n " + "\nAvailable commands: " + "\n modules List registered modules " + "\n backends List registered backends and their status " + "\n models List registered models and output model graph " + "\n capabilities List all registered function capabilities " + "\n scanners List registered scanners " + "\n test-functions List registered scanner test objective functions " + "\n Give info on a specific module, module function, " + "\n backend, backend function, model, capability, " + "\n scanner or scanner test objective function " + "\n e.g.: gambit DarkBit " + "\n gambit GA_SimYieldTable_DarkSUSY " + "\n gambit Pythia " + "\n gambit get_abund_map_AlterBBN " + "\n gambit MSSM " + "\n gambit IC79WL_loglike " + "\n gambit MultiNest " + "\n " + "\nBasic options: " + "\n --version Display GAMBIT version information " + "\n -h/--help Display this usage information " + "\n -f Start scan using " + "\n -v/--verbose Turn on verbose mode " + "\n -d/--dryrun List the function evaluation order computed based " + "\n on inifile " + "\n -b/--backends List the backends required to fulfil dependencies " + "\n based on inifile " + "\n -r/--restart Restart the scan defined by . Existing " + "\n output files for the run will be overwritten. " + "\n Default behaviour in the absence of this option is" + "\n to attempt to resume the scan from any existing " + "\n output. " + "\n\n\n"; + +} + +#endif // defined __gambit_cli_help_text_hpp__ \ No newline at end of file diff --git a/Core/src/core.cpp b/Core/src/core.cpp index fbc1e01e28..8d9f1bd3a2 100644 --- a/Core/src/core.cpp +++ b/Core/src/core.cpp @@ -39,6 +39,7 @@ // Gambit headers #include "gambit/Core/core.hpp" +#include "gambit/Core/cli_help_text.hpp" #include "gambit/Core/error_handlers.hpp" #include "gambit/Core/yaml_description_database.hpp" #include "gambit/ScannerBit/plugin_loader.hpp" @@ -81,50 +82,7 @@ namespace Gambit void gambit_core::bail(int mpirank) { if (mpirank < 0) mpirank = GET_RANK; - if (mpirank == 0) - { - cout << "\nusage: gambit [options] [] " - "\n " - "\nRun scan: " - "\n gambit -f Start a scan using instructions from inifile " - "\n e.g.: gambit -f gambit.yaml " - "\n " - "\nAvailable commands: " - "\n modules List registered modules " - "\n backends List registered backends and their status " - "\n models List registered models and output model graph " - "\n capabilities List all registered function capabilities " - "\n scanners List registered scanners " - "\n test-functions List registered scanner test objective functions " - "\n Give info on a specific module, module function, " - "\n backend, backend function, model, capability, " - "\n scanner or scanner test objective function " - "\n e.g.: gambit DarkBit " - "\n gambit GA_SimYieldTable_DarkSUSY " - "\n gambit Pythia " - "\n gambit get_abund_map_AlterBBN " - "\n gambit MSSM " - "\n gambit IC79WL_loglike " - "\n gambit MultiNest " - "\n " - "\nBasic options: " - "\n --version Display GAMBIT version information " - "\n -h/--help Display this usage information " - "\n -f Start scan using " - "\n -v/--verbose Turn on verbose mode " - "\n -d/--dryrun List the function evaluation order computed based " - "\n on inifile " - "\n -b/--backends List the backends required to fulfil dependencies " - "\n based on inifile " - "\n -r/--restart Restart the scan defined by . Existing " - "\n output files for the run will be overwritten. " - "\n Default behaviour in the absence of this option is" - "\n to attempt to resume the scan from any existing " - "\n output. " - "\n" - << endl - << endl; - } + if (mpirank == 0) cout << cli_help_text; // from cli_help_text.hpp logger().disable(); throw SilentShutdownException(); } diff --git a/cmake/contrib.cmake b/cmake/contrib.cmake index ca6b059d91..7388833f3a 100644 --- a/cmake/contrib.cmake +++ b/cmake/contrib.cmake @@ -70,7 +70,7 @@ endfunction() set(name "gambit_preload") set(dir "${CMAKE_BINARY_DIR}/contrib") add_library(${name} SHARED "${PROJECT_SOURCE_DIR}/contrib/preload/gambit_preload.cpp") -target_include_directories(${name} PRIVATE "${PROJECT_SOURCE_DIR}/cmake/include" "${PROJECT_SOURCE_DIR}/Utils/include") +target_include_directories(${name} PRIVATE "${PROJECT_SOURCE_DIR}/cmake/include" "${PROJECT_SOURCE_DIR}/Core/include" "${PROJECT_SOURCE_DIR}/Utils/include") set_target_properties(${name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${dir}" LIBRARY_OUTPUT_DIRECTORY "${dir}" diff --git a/contrib/preload/gambit_preload.cpp b/contrib/preload/gambit_preload.cpp index 44ee743986..2b22ea915f 100644 --- a/contrib/preload/gambit_preload.cpp +++ b/contrib/preload/gambit_preload.cpp @@ -17,6 +17,7 @@ /// \author Anders Kvellestad /// anders.kvellestad@fys.uio.no /// \date 2023 Oct +/// \date 2026 May /// /// ********************************************* @@ -25,9 +26,60 @@ #include #include "gambit/cmake/cmake_variables.hpp" +#include "gambit/Core/cli_help_text.hpp" #include "gambit/Utils/stringify.hpp" +// Classify the current gambit invocation by inspecting /proc/self/cmdline. +// Used by the constructor below to short-circuit trivial flag-only invocations +// (--help, -h, --version, no-args) before the main binary's C++ static +// initialisers run, saving several seconds of startup on e.g. `./gambit --help`. +// +// Detection is Linux-only (/proc/self/cmdline). On other systems this returns +// "normal" and the existing fast-paths in main()/run_diagnostic still cover +// the trivial invocations, just a bit slower. +typedef enum +{ + GAMBIT_INVOCATION_NORMAL = 0, + GAMBIT_INVOCATION_HELP = 1, // --help, -h, or no arguments at all + GAMBIT_INVOCATION_VERSION = 2, // --version +} gambit_invocation_kind; + +static gambit_invocation_kind gambit_classify_invocation() +{ +#ifdef __linux__ + FILE* f = fopen("/proc/self/cmdline", "r"); + if (f == NULL) return GAMBIT_INVOCATION_NORMAL; + char buf[4096]; + size_t n = fread(buf, 1, sizeof(buf) - 1, f); + fclose(f); + if (n == 0) return GAMBIT_INVOCATION_NORMAL; + buf[n] = '\0'; + // Step past argv[0] (the executable path). + size_t i = 0; + while (i < n && buf[i] != '\0') ++i; + ++i; + // No further arguments: print usage like `./gambit --help` does. + if (i >= n) return GAMBIT_INVOCATION_HELP; + // Walk argv looking for the first matching trivial flag, mirroring + // getopt's left-to-right parse order. + while (i < n) + { + const char* arg = &buf[i]; + if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) + return GAMBIT_INVOCATION_HELP; + if (strcmp(arg, "--version") == 0) + return GAMBIT_INVOCATION_VERSION; + while (i < n && buf[i] != '\0') ++i; + ++i; + } + return GAMBIT_INVOCATION_NORMAL; +#else + return GAMBIT_INVOCATION_NORMAL; +#endif +} + + // Initializer; runs as soon as this library is loaded. __attribute__((constructor)) static void initializer() @@ -37,6 +89,35 @@ static void initializer() if (strcmp(GAMBIT_VERSION_PATCH, "") != 0) printf("%s", "-" GAMBIT_VERSION_PATCH); printf("\nhttp://gambitbsm.org\n\n\x1b[0m"); + // Trivial flag-only invocations (--help, -h, --version, no-args) + // don't need anything else GAMBIT does at startup, so print the + // appropriate output and exit *here*, before the dynamic loader + // hands control to the executable's .init_array. + // + // The fast-paths in main() and run_diagnostic remain in place + // as a safety net for non-Linux systems where /proc/self/cmdline + // isn't available. + { + const gambit_invocation_kind kind = gambit_classify_invocation(); + if (kind == GAMBIT_INVOCATION_HELP) + { + fputs(Gambit::cli_help_text, stdout); + exit(0); + } + if (kind == GAMBIT_INVOCATION_VERSION) + { + // The banner above is the version output; no further text needed. + exit(0); + } + if (kind == GAMBIT_INVOCATION_NORMAL) + { + // Normal GAMBIT invocation usually takes a few seconds of startup + // time, so let's inform the user that we are working on it. + printf("Initialising GAMBIT...\n"); + } + } + + // Set environment variable for RestFrames #ifndef EXCLUDE_RESTFRAMES {