The Gaudi Framework  master (76fc3702)
Loading...
Searching...
No Matches
PluginServiceV2.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 2013-2026 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
11
13
14#define GAUDI_PLUGIN_SERVICE_V2
15#include <Gaudi/PluginService.h>
16
17#include <dirent.h>
18#include <dlfcn.h>
19
20#include <cstdlib>
21#include <fstream>
22#include <iostream>
23#include <memory>
24#include <regex>
25#include <string_view>
26
27#include <cxxabi.h>
28#include <sys/stat.h>
29
30#ifdef _GNU_SOURCE
31# include <cstring>
32# include <dlfcn.h>
33#endif
34
35#ifdef USE_BOOST_FILESYSTEM
36# include <boost/filesystem.hpp>
37namespace fs = boost::filesystem;
38#else
39# include <filesystem>
40namespace fs = std::filesystem;
41#endif // USE_BOOST_FILESYSTEM
42
43namespace {
44 std::mutex registrySingletonMutex;
45}
46
47#include <algorithm>
48#include <array>
49
50namespace {
51 struct OldStyleCnv {
52 std::string name;
53 void operator()( const char c ) {
54 switch ( c ) {
55 case '<':
56 case '>':
57 case ',':
58 case '(':
59 case ')':
60 case ':':
61 case '.':
62 name.push_back( '_' );
63 break;
64 case '&':
65 name.push_back( 'r' );
66 break;
67 case '*':
68 name.push_back( 'p' );
69 break;
70 case ' ':
71 break;
72 default:
73 name.push_back( c );
74 break;
75 }
76 }
77 };
79 std::string old_style_name( const std::string& name ) {
80 return std::for_each( name.begin(), name.end(), OldStyleCnv() ).name;
81 }
82
83 // helper to locate the current DSO
84 int _dso_marker() { return 0; }
85} // namespace
86
87namespace Gaudi {
88 namespace PluginService {
90 namespace Details {
91 std::string demangle( const std::string& id ) {
92 int status;
93 auto realname = std::unique_ptr<char, decltype( free )*>(
94 abi::__cxa_demangle( id.c_str(), nullptr, nullptr, &status ), free );
95 if ( !realname ) return id;
96 std::string result = realname.get();
97 // Normalize libstdc++ (Linux) std::string representation
98 result = std::regex_replace(
99 result,
100 std::regex{ "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >( (?=>))?" },
101 "std::string" );
102 // Normalize libc++ (macOS) inline namespace - remove std::__1:: prefix
103 result = std::regex_replace( result, std::regex{ "std::__1::" }, "std::" );
104 // Normalize libc++ basic_string (after removing __1::)
105 result = std::regex_replace(
106 result, std::regex{ "std::basic_string<char, std::char_traits<char>, std::allocator<char>>( (?=>))?" },
107 "std::string" );
108 // Normalize closing angle brackets: >> to > > (match libstdc++ C++03 style)
109 result = std::regex_replace( result, std::regex{ ">>" }, "> >" );
110 result = std::regex_replace( result, std::regex{ ">>" }, "> >" ); // twice for >>>
111 return result;
112 }
113 std::string demangle( const std::type_info& id ) { return demangle( id.name() ); }
114
115 Registry& Registry::instance() {
116 auto _guard = std::scoped_lock{ ::registrySingletonMutex };
117 static Registry r;
118 return r;
119 }
120
121 void reportBadAnyCast( const std::type_info& factory_type, const std::string& id ) {
122 if ( logger().level() <= Logger::Debug ) {
123 std::stringstream msg;
124 const auto& info = Registry::instance().getInfo( id );
125 msg << "bad any_cast: requested factory " << id << " of type " << demangle( factory_type ) << ", got ";
126 if ( info.is_set() )
127 msg << demangle( info.factory.type() ) << " from " << info.library;
128 else
129 msg << "nothing";
130 logger().debug( msg.str() );
131 }
132 }
133
134 Registry::Properties::mapped_type Registry::FactoryInfo::getprop( const Properties::key_type& name ) const {
135 auto p = properties.find( name );
136 return ( p != end( properties ) ) ? p->second : Properties::mapped_type{};
137 }
138
139 Registry::Registry() {}
140
141 void Registry::initialize() {
142 auto _guard = std::scoped_lock{ m_mutex };
143
144 std::regex line_format{ "^(?:[[:space:]]*(?:(v[0-9]+)::)?([^:]+):(.*[^[:space:]]))?[[:space:]]*(?:#.*)?$" };
145 std::smatch m;
146 for ( const auto& dir : getPluginSearchPath() ) {
147 // correctly handle begin of string or path separator
148 logger().debug( " looking into " + dir );
149 // look for files called "*.components" in the directory
150 if ( !fs::is_directory( dir ) ) { continue; }
151 for ( const auto& p : fs::directory_iterator( dir ) ) {
152 if ( p.path().extension() != ".components" || !is_regular_file( p.path() ) ) { continue; }
153 // read the file
154 const auto& fullPath = p.path().string();
155 logger().debug( " reading " + p.path().filename().string() );
156 std::ifstream factories{ fullPath };
157 std::string line;
158 int factoriesCount = 0;
159 int lineCount = 0;
160 while ( !factories.eof() ) {
161 ++lineCount;
162 std::getline( factories, line );
163 if ( regex_match( line, m, line_format ) ) {
164 if ( m[1] != "v2" ) { continue; } // ignore non "v2" and "empty" lines
165 const std::string lib{ m[2] };
166 const std::string fact{ m[3] };
167 m_factories.emplace( fact, FactoryInfo{ lib, {}, { { "ClassName", fact } } } );
168#ifdef GAUDI_REFLEX_COMPONENT_ALIASES
169 // add an alias for the factory using the Reflex convention
170 std::string old_name = old_style_name( fact );
171 if ( fact != old_name ) {
172 m_factories.emplace( old_name,
173 FactoryInfo{ lib, {}, { { "ReflexName", "true" }, { "ClassName", fact } } } );
174 }
175#endif
176 ++factoriesCount;
177 } else {
178 logger().warning( "failed to parse line " + fullPath + ':' + std::to_string( lineCount ) );
179 }
180 }
181 if ( logger().level() <= Logger::Debug ) {
182 logger().debug( " found " + std::to_string( factoriesCount ) + " factories" );
183 }
184 }
185 }
186 }
187
188 const Registry::FactoryMap& Registry::factories() const {
189 std::call_once( m_initialized, &Registry::initialize, const_cast<Registry*>( this ) );
190 return m_factories;
191 }
192
193 Registry::FactoryMap& Registry::factories() {
194 std::call_once( m_initialized, &Registry::initialize, this );
195 return m_factories;
196 }
197
198 Registry::FactoryInfo& Registry::add( const KeyType& id, FactoryInfo info ) {
199 auto _guard = std::scoped_lock{ m_mutex };
200 FactoryMap& facts = factories();
201
202#ifdef GAUDI_REFLEX_COMPONENT_ALIASES
203 // add an alias for the factory using the Reflex convention
204 const auto old_name = old_style_name( id );
205 if ( id != old_name ) {
206 auto new_info = info;
207
208 new_info.properties["ReflexName"] = "true";
209
210 add( old_name, new_info );
211 }
212#endif
213
214 auto entry = facts.find( id );
215 if ( entry == facts.end() ) {
216 // this factory was not known yet
217 entry = facts.emplace( id, std::move( info ) ).first;
218 } else {
219 // do not replace an existing factory with a new one
220 if ( !entry->second.is_set() ) entry->second = std::move( info );
221 }
222 return entry->second;
223 }
224
225 Registry::FactoryMap::size_type Registry::erase( const KeyType& id ) {
226 auto _guard = std::scoped_lock{ m_mutex };
227 FactoryMap& facts = factories();
228 return facts.erase( id );
229 }
230
231 const Registry::FactoryInfo& Registry::getInfo( const KeyType& id, const bool load ) const {
232 auto _guard = std::scoped_lock{ m_mutex };
233 static const FactoryInfo unknown = { "unknown" };
234 const FactoryMap& facts = factories();
235 auto f = facts.find( id );
236
237 if ( f == facts.end() ) { return unknown; }
238 if ( !load || f->second.is_set() ) { return f->second; }
239
240 if ( !loadPluginLibrary( f->second.library ) ) { return unknown; }
241
242 f = facts.find( id ); // ensure that the iterator is valid
243 return f->second;
244 }
245
246 Registry& Registry::addProperty( const KeyType& id, const KeyType& k, const std::string& v ) {
247 auto _guard = std::scoped_lock{ m_mutex };
248 FactoryMap& facts = factories();
249 auto f = facts.find( id );
250
251 if ( f != facts.end() ) f->second.properties[k] = v;
252 return *this;
253 }
254
255 void Registry::setError( const KeyType& warning ) { m_werror.insert( warning ); }
256
257 void Registry::unsetError( const KeyType& warning ) { m_werror.erase( warning ); }
258
259 std::set<Registry::KeyType> Registry::loadedFactoryNames() const {
260 auto _guard = std::scoped_lock{ m_mutex };
261 std::set<KeyType> l;
262 for ( const auto& f : factories() ) {
263 if ( f.second.is_set() ) l.insert( f.first );
264 }
265 return l;
266 }
267
268 bool Registry::loadPluginLibrary( std::string_view library ) const {
269 for ( const auto& dir : getPluginSearchPath() ) {
270 if ( !fs::exists( dir ) ) { continue; }
271 const auto path = dir / fs::path( library );
272 if ( is_regular_file( path ) ) {
273 if ( dlopen( path.c_str(), RTLD_LAZY | RTLD_GLOBAL ) ) {
274 return true;
275 } else {
276 logger().warning( "cannot load " + path.string() );
277 if ( char* dlmsg = dlerror() ) { logger().warning( dlmsg ); }
278 }
279 }
280 }
281 // not found yet, let's see if dlopen can find it via LD_LIBRARY_PATH
282 if ( dlopen( std::string{ library }.c_str(), RTLD_LAZY | RTLD_GLOBAL ) ) { return true; }
283 // still no luck
284 logger().warning( "cannot load " + std::string{ library } );
285 if ( char* dlmsg = dlerror() ) { logger().warning( dlmsg ); }
286 return false;
287 }
288
289 void Logger::report( Level lvl, const std::string& msg ) {
290 static const char* levels[] = { "DEBUG : ", "INFO : ", "WARNING: ", "ERROR : " };
291 if ( lvl >= level() ) { std::cerr << levels[lvl] << msg << std::endl; }
292 }
293
294 static auto s_logger = std::make_unique<Logger>();
295 Logger& logger() { return *s_logger; }
296 void setLogger( Logger* logger ) { s_logger.reset( logger ); }
297
298 // This chunk of code was taken from GaudiKernel (genconf) DsoUtils.h
299 std::string getDSONameFor( void* fptr ) {
300#if defined _GNU_SOURCE || defined __APPLE__
301 Dl_info info;
302 if ( dladdr( fptr, &info ) == 0 ) return "";
303
304 auto pos = std::strrchr( info.dli_fname, '/' );
305 if ( pos )
306 ++pos;
307 else
308 return info.dli_fname;
309 return pos;
310#else
311 return "";
312#endif
313 }
314
315 std::string implicitPluginDir() {
316#ifdef PLUGIN_PATH_RELATIVE
317# define stringify( x ) stringify_( x )
318# define stringify_( x ) #x
319 std::string relative_path = stringify( PLUGIN_PATH_RELATIVE );
320#else
321 std::string relative_path = ".";
322#endif
323 Dl_info info;
324 if ( dladdr( (void*)_dso_marker, &info ) != 0 ) {
325 auto plugin_path = fs::path( info.dli_fname ).parent_path() / relative_path;
326 return plugin_path.string();
327 } else {
328 return relative_path;
329 }
330 }
331
332 const std::vector<std::string>& getPluginSearchPath() {
333 // we search for plugin libraries in the following order:
334 // 1. GAUDI_PLUGIN_PATH environment variable
335 // 2. LD_LIBRARY_PATH environment variable (backward compatibility)
336 // 3. implicit plugin path (relative to the library)
337
338 constexpr char const* envVars[] =
339#if defined( __APPLE__ )
340 { "GAUDI_PLUGIN_PATH", "DYLD_LIBRARY_PATH" };
341#else
342 { "GAUDI_PLUGIN_PATH", "LD_LIBRARY_PATH" };
343#endif
344
345 // We cache the result to avoid recomputing it on every call
346 static std::vector<std::string> search_path;
347 static std::once_flag initialized;
348 std::call_once( initialized, [&]() {
349 for ( const auto& envVar : envVars ) {
350 if ( auto ptr = std::getenv( envVar ) ) {
351 std::string_view path{ ptr };
352 while ( !path.empty() ) {
353 auto sep = path.find( ':' );
354 std::string dirname = std::string{ path.substr( 0, sep ) };
355 // ignore duplicates
356 if ( find( search_path.begin(), search_path.end(), dirname ) == search_path.end() ) {
357 search_path.emplace_back( std::move( dirname ) );
358 }
359 // handle correctly missing separator
360 if ( sep != std::string_view::npos ) {
361 path = path.substr( sep + 1 );
362 } else {
363 path = {};
364 }
365 }
366 }
367 }
368 search_path.push_back( implicitPluginDir() );
369 } );
370 return search_path;
371 }
372 } // namespace Details
373
374 void SetDebug( int debugLevel ) {
375 using namespace Details;
376 Logger& l = logger();
377 if ( debugLevel > 1 )
378 l.setLevel( Logger::Debug );
379 else if ( debugLevel > 0 )
380 l.setLevel( Logger::Info );
381 else
382 l.setLevel( Logger::Warning );
383 }
384
385 int Debug() {
386 using namespace Details;
387 switch ( logger().level() ) {
388 case Logger::Debug:
389 return 2;
390 case Logger::Info:
391 return 1;
392 default:
393 return 0;
394 }
395 }
396 }
397 } // namespace PluginService
398} // namespace Gaudi
#define GAUDI_PLUGIN_SERVICE_V2_INLINE
std::string demangle(const std::string &id)
const std::vector< std::string > & getPluginSearchPath()
void reportBadAnyCast(const std::type_info &factory_type, const std::string &id)
std::string getDSONameFor(void *fptr)
void SetDebug(int debugLevel)
constexpr double m
GAUDI_API std::string path(const AIDA::IBaseHistogram *aida)
get the path in THS for AIDA histogram
This file provides a Grammar for the type Gaudi::Accumulators::Axis It allows to use that type from p...
Definition __init__.py:1
dict l
Definition gaudirun.py:583