Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Core/include/gambit/Core/cli_help_text.hpp
Original file line number Diff line number Diff line change
@@ -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] [<command>] "
"\n "
"\nRun scan: "
"\n gambit -f <inifile> 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 <name> 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 <inifile> Start scan using <inifile> "
"\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 <inifile>. 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__
58 changes: 14 additions & 44 deletions Core/src/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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] [<command>] "
"\n "
"\nRun scan: "
"\n gambit -f <inifile> 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 <name> 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 <inifile> Start scan using <inifile> "
"\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 <inifile>. 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();
}
Expand Down Expand Up @@ -635,6 +593,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<str> valid_commands = initVector<str>("modules", "backends", "models", "capabilities", "scanners", "test-functions");

Expand Down
2 changes: 1 addition & 1 deletion cmake/contrib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
81 changes: 81 additions & 0 deletions contrib/preload/gambit_preload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/// \author Anders Kvellestad
/// anders.kvellestad@fys.uio.no
/// \date 2023 Oct
/// \date 2026 May
///
/// *********************************************

Expand All @@ -25,9 +26,60 @@
#include <cstring>

#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()
Expand All @@ -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
{
Expand Down
Loading