Skip to content

Commit f6ec9c2

Browse files
committed
added GEMC_PLUGIN_PATH and fixed case where nullptr for GDigitizedData was not picked up in gEventAction.cc
1 parent 81da0ee commit f6ec9c2

9 files changed

Lines changed: 102 additions & 25 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,17 @@ concurrency:
1616
cancel-in-progress: true
1717

1818
on:
19+
workflow_dispatch:
1920
pull_request:
2021
paths-ignore:
2122
- "**/*.md"
22-
- "**/README*"
23-
- "**/LICENSE*"
24-
- "**/CODE_OF_CONDUCT.md"
25-
- "**/CONTRIBUTING.md"
26-
- "**/SECURITY.md"
23+
2724
merge_group:
2825
push:
2926
branches: [ main ]
3027
tags: [ '*' ]
3128
paths-ignore:
3229
- "**/*.md"
33-
- "**/README*"
34-
- "**/CODE_OF_CONDUCT.md"
35-
- "**/CONTRIBUTING.md"
36-
- "**/SECURITY.md"
37-
- "releases/**"
38-
- "doc/**"
3930

4031
jobs:
4132
discover:

gemc/actions/event/gEventAction.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "../gactionConventions.h"
33

44
// geant4
5-
#include "G4EventManager.hh"
5+
#include "G4Event.hh"
66
#include "G4Threading.hh"
77

88
// gemc
@@ -140,7 +140,9 @@ void GEventAction::EndOfEventAction([[maybe_unused]] const G4Event* event) {
140140
auto digi_data = digitization_routine->digitizeHit(this_hit, hitIndex);
141141

142142
if (collection_mode == CollectionMode::event) {
143-
eventDataCollection->addDetectorDigitizedData(hcSDName, std::move(digi_data));
143+
if (digi_data != nullptr) {
144+
eventDataCollection->addDetectorDigitizedData(hcSDName, std::move(digi_data));
145+
}
144146
eventDataCollection->addDetectorTrueInfoData(hcSDName, std::move(true_data));
145147
has_event_mode_payload = true;
146148
}

gemc/gfactory/gdl.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include <dlfcn.h>
1010

1111
// c++
12+
#include <cstdlib>
13+
#include <filesystem>
14+
#include <sstream>
1215
#include <sys/stat.h> // to check if file exists
1316
#include <string>
1417

@@ -83,6 +86,24 @@ struct DynamicLib
8386
log->debug(CONSTRUCTOR, "Instantiating ", path);
8487
log->debug(NORMAL, "Trying ", dlFileName);
8588

89+
// Try GEMC_PLUGIN_PATH directories before falling back to gemc installation paths.
90+
if (!doesFileExists(dlFileName)) {
91+
log->debug(NORMAL, dlFileName, " not found...");
92+
93+
if (const char* envPath = std::getenv("GEMC_PLUGIN_PATH")) {
94+
std::istringstream ss(envPath);
95+
std::string dir;
96+
while (std::getline(ss, dir, ':')) {
97+
const std::string candidate = dir + "/" + path;
98+
if (doesFileExists(candidate)) {
99+
dlFileName = candidate;
100+
break;
101+
}
102+
}
103+
}
104+
log->debug(NORMAL, "Trying ", dlFileName);
105+
}
106+
86107
// Try installation path + "/lib" if not found at the provided location.
87108
if (!doesFileExists(dlFileName)) {
88109
log->debug(NORMAL, dlFileName, " not found...");
@@ -113,7 +134,14 @@ struct DynamicLib
113134
}
114135
else { log->info(1, "Loaded ", dlFileName); }
115136
}
116-
else { log->error(ERR_DLNOTFOUND, "could not find ", dlFileName); }
137+
else {
138+
const char* envPath = std::getenv("GEMC_PLUGIN_PATH");
139+
log->error(ERR_DLNOTFOUND,
140+
"could not find ", dlFileName, "\n",
141+
" GEMC_PLUGIN_PATH = ", (envPath ? envPath : "(not set)"), "\n",
142+
" Hint: set GEMC_PLUGIN_PATH or use -plugin_path=<dir> to the directory ",
143+
"containing *.gplugin files.");
144+
}
117145
}
118146

119147
/**

gemc/gfactory/gfactory.h

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
*/
1414

1515
// C++
16+
#include <filesystem>
1617
#include <memory>
18+
#include <sstream>
1719
#include <string>
1820
#include <string_view>
1921
#include <unordered_map>
@@ -128,7 +130,7 @@ class GManager : public GBase<GManager>
128130
*
129131
* \note The manager uses the `PLUGIN_LOGGER` channel for plugin-related output.
130132
*/
131-
explicit GManager(const std::shared_ptr<GOptions>& gopt) : GBase(gopt, PLUGIN_LOGGER) {
133+
explicit GManager(const std::shared_ptr<GOptions>& gopt) : GBase(gopt, PLUGIN_LOGGER), gopts_(gopt) {
132134
}
133135

134136
/// No copy – the manager owns unique resources (factory objects and loaded libraries).
@@ -218,6 +220,9 @@ class GManager : public GBase<GManager>
218220
/// Map from plugin key to the loaded library handle.
219221
std::unordered_map<std::string, std::shared_ptr<DynamicLib>> dlMap_;
220222

223+
/// Options retained so registerDL can read plugin_path at load time.
224+
std::shared_ptr<GOptions> gopts_;
225+
221226
/// Optional human-readable manager name (currently unused here).
222227
std::string gname;
223228
};
@@ -230,14 +235,32 @@ inline void GManager::clearDLMap() noexcept {
230235
}
231236

232237
inline void GManager::registerDL(std::string_view name) {
233-
// Convention: plugins are packaged as "<name>.gplugin".
234-
const std::string filename = std::string{name} + ".gplugin";
238+
const std::string basename = std::string{name} + ".gplugin";
239+
240+
// Build the colon-separated search path: -plugin_path option takes
241+
// precedence, then GEMC_PLUGIN_PATH env var, then CWD / system library path.
242+
std::string filename = basename;
243+
if (gopts_) {
244+
std::string searchPath = gopts_->getScalarString("plugin_path");
245+
if (searchPath.empty()) {
246+
if (const char* env = std::getenv("GEMC_PLUGIN_PATH")) { searchPath = env; }
247+
}
248+
if (!searchPath.empty()) {
249+
std::istringstream ss(searchPath);
250+
std::string dir;
251+
while (std::getline(ss, dir, ':')) {
252+
const std::string candidate = dir + "/" + basename;
253+
if (std::filesystem::exists(candidate)) {
254+
filename = candidate;
255+
break;
256+
}
257+
}
258+
}
259+
}
235260

236-
// Store the DynamicLib in a shared_ptr so it can be safely captured by
237-
// a shared_ptr deleter in LoadAndRegisterObjectFromLibrary().
238261
dlMap_.emplace(std::string{name},
239262
std::make_shared<DynamicLib>(log, filename));
240-
log->debug(NORMAL, "Loading DL ", name);
263+
log->debug(NORMAL, "Loading DL ", name, " (resolved: ", filename, ")");
241264
}
242265

243266
template <class Derived>
@@ -291,5 +314,10 @@ std::shared_ptr<T> GManager::LoadAndRegisterObjectFromLibrary(std::string_view
291314
});
292315
}
293316

294-
log->error(ERR_DLHANDLENOTFOUND, "Plugin ", name, " could not be loaded.");
317+
const char* envPath = std::getenv("GEMC_PLUGIN_PATH");
318+
log->error(ERR_DLHANDLENOTFOUND,
319+
"Plugin ", name, ".gplugin could not be loaded.\n",
320+
" GEMC_PLUGIN_PATH = ", (envPath ? envPath : "(not set)"), "\n",
321+
" Hint: set GEMC_PLUGIN_PATH or use -plugin_path=<dir> to point to the directory ",
322+
"containing *.gplugin files.");
295323
}

gemc/gfactory/gfactory_options.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
// project goption to a system
44
namespace gfactory {
55

6-
// returns array of options definitions
76
GOptions defineOptions() {
87

9-
// Construct the options container for the plugin logger channel.
10-
// Individual flags can be added here as the module evolves.
118
GOptions goptions(PLUGIN_LOGGER);
129

10+
goptions.defineOption(
11+
GVariable("plugin_path", "", "colon-separated list of directories to search for .gplugin files"),
12+
"Additional directories searched for GEMC plugin libraries (*.gplugin) before the\n"
13+
"current working directory and the system library path (LD_LIBRARY_PATH / DYLD_LIBRARY_PATH).\n"
14+
"Example: -plugin_path=/opt/clas12/lib:/usr/local/gemc/plugins"
15+
);
16+
1317
return goptions;
1418
}
1519

gemc/gstreamer/factories/ROOT/gstreamerROOTFactory.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateHeaderTr
7474
const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateTrueInfoDataTree(
7575
const std::string& detectorName,
7676
const GTrueInfoData* gdata) {
77+
rootfile->cd();
7778
std::string treeName = TRUEINFONAMEPREFIX + detectorName;
7879

7980
auto& treePtr = gRootTrees[treeName];
@@ -90,6 +91,7 @@ const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateTrueInfo
9091
const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateDigitizedDataTree(
9192
const std::string& detectorName,
9293
const GDigitizedData* gdata) {
94+
rootfile->cd();
9395
std::string treeName = DIGITIZEDNAMEPREFIX + detectorName;
9496

9597
auto& treePtr = gRootTrees[treeName];
@@ -104,6 +106,7 @@ const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateDigitize
104106
const std::unique_ptr<GRootTree>& GstreamerRootFactory::getOrInstantiateGeneratedParticleTree(
105107
const std::string& treeName,
106108
const GGeneratedParticleBank& particles) {
109+
rootfile->cd();
107110
auto& treePtr = gRootTrees[treeName];
108111
if (!treePtr) {
109112
log->info(2, "GstreamerRootFactory", "Creating generated-particle ROOT tree for ", treeName);

meson.build

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ pkg.generate(
247247
version : meson.project_version(),
248248
libraries : all_libs,
249249
libraries_private : [yaml_cpp_dep, clhep_deps, geant4_core_deps, zlib_dep],
250-
subdirs : 'gemc',
251250
)
252251

253252
# test gemc options. Adjust root to fit the geometry

meson/meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ assimp_dep = assimp_proj.dependency('assimp')
174174
yaml_cpp_proj = cmake.subproject('yaml-cpp', options : cmake_opts)
175175
yaml_cpp_dep = yaml_cpp_proj.dependency('yaml-cpp')
176176

177+
# yaml-cpp headers are part of gemc's public API (goption.h includes yaml-cpp/yaml.h)
178+
# so they must be installed alongside the gemc headers.
179+
install_subdir(
180+
meson.project_source_root() / 'subprojects' / 'yaml-cpp' / 'include' / 'yaml-cpp',
181+
install_dir : get_option('includedir'),
182+
)
183+
177184
# sanitizer settings
178185
project_test_env = environment()
179186
project_test_env.set(

releases/0.3.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This version includes:
88
- Theme-aware SVG toggle icons for display options
99
- Volume inspection from the geometry tree
1010
- Unified `value*unit` notation for `gparticle` kinematic fields
11+
- `GEMC_PLUGIN_PATH` and `-plugin_path` for external plugin discovery
12+
- ROOT gstreamer and event-action bug fixes
1113

1214
<br/>
1315

@@ -323,6 +325,19 @@ Both x86_64 and ARM64 platforms are supported.
323325
`/vis/scene/add/volume <name> -1`, sets white background and a `text2D` label,
324326
flushes, then restores focus with `/vis/viewer/select`; button text is set to
325327
"Inspect `<volumename>`" when a volume node is selected in the tree.
328+
- Added `plugin_path` option and `GEMC_PLUGIN_PATH` environment variable support to `GManager::registerDL`
329+
so plugins installed to a separate prefix are found without touching `LD_LIBRARY_PATH`.
330+
- Extended `DynamicLib` constructor to search `GEMC_PLUGIN_PATH` directories before the existing
331+
`<gemc_root>/lib/` and `<gemc_root>/build/` fallbacks; diagnostic output on failure shows the current
332+
env-var value and a usage hint for both `GEMC_PLUGIN_PATH` and `-plugin_path`.
333+
- Fixed a segmentation fault in the ROOT gstreamer plugin: `getOrInstantiateDigitizedDataTree`,
334+
`getOrInstantiateTrueInfoDataTree`, and `getOrInstantiateGeneratedParticleTree` now call `rootfile->cd()`
335+
before creating `TTree` objects, matching the existing pattern in `getOrInstantiateHeaderTree`; without
336+
this, ROOT's thread-local `gDirectory` on a Geant4 worker thread does not point at the output file.
337+
- Fixed event-mode null-pointer crash in `gEventAction.cc`: `GDigitizedData` pointers returned as `nullptr`
338+
by `digitizeHitImpl` (the base-class default for plugins that have not yet implemented digitization) are now
339+
skipped instead of being inserted into the collection, matching the existing run-mode null guard and
340+
preventing the ROOT streamer from dereferencing a null pointer when building the tree schema.
326341
- Removed `punit`, `aunit`, and `vunit` from the `gparticle` option schema.
327342
Added `parseG4Value()` in `gparticle_options.cc`: reads each kinematic YAML
328343
field as a string, calls `gutilities::getG4Number()` directly when a `*unit`

0 commit comments

Comments
 (0)