The Gaudi Framework  master (2e52acd2)
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
49namespace {
50 struct OldStyleCnv {
51 std::string name;
52 void operator()( const char c ) {
53 switch ( c ) {
54 case '<':
55 case '>':
56 case ',':
57 case '(':
58 case ')':
59 case ':':
60 case '.':
61 name.push_back( '_' );
62 break;
63 case '&':
64 name.push_back( 'r' );
65 break;
66 case '*':
67 name.push_back( 'p' );
68 break;
69 case ' ':
70 break;
71 default:
72 name.push_back( c );
73 break;
74 }
75 }
76 };
78 std::string old_style_name( const std::string& name ) {
79 return std::for_each( name.begin(), name.end(), OldStyleCnv() ).name;
80 }
81
82 // helper to locate the current DSO
83 int _dso_marker() { return 0; }
84} // namespace
85
86namespace Gaudi {
87 namespace PluginService {
89 namespace Details {
90 std::string demangle( const std::string& id ) {
91 int status;
92 auto realname = std::unique_ptr<char, decltype( free )*>(
93 abi::__cxa_demangle( id.c_str(), nullptr, nullptr, &status ), free );
94 if ( !realname ) return id;
95 std::string result = realname.get();
96 // Normalize libstdc++ (Linux) std::string representation
97 result = std::regex_replace(
98 result,
99 std::regex{ "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >( (?=>))?" },
100 "std::string" );
101 // Normalize libc++ (macOS) inline namespace - remove std::__1:: prefix
102 result = std::regex_replace( result, std::regex{ "std::__1::" }, "std::" );
103 // Normalize libc++ basic_string (after removing __1::)
104 result = std::regex_replace(
105 result, std::regex{ "std::basic_string<char, std::char_traits<char>, std::allocator<char>>( (?=>))?" },
106 "std::string" );
107 // Normalize closing angle brackets: >> to > > (match libstdc++ C++03 style)
108 result = std::regex_replace( result, std::regex{ ">>" }, "> >" );
109 result = std::regex_replace( result, std::regex{ ">>" }, "> >" ); // twice for >>>
110 return result;
111 }
112 std::string demangle( const std::type_info& id ) { return demangle( id.name() ); }
113
114 Registry& Registry::instance() {
115 auto _guard = std::scoped_lock{ ::registrySingletonMutex };
116 static Registry r;
117 return r;
118 }
119
120 void reportBadAnyCast( const std::type_info& factory_type, const std::string& id ) {
121 if ( logger().level() <= Logger::Debug ) {
122 std::stringstream msg;
123 const auto& info = Registry::instance().getInfo( id );
124 msg << "bad any_cast: requested factory " << id << " of type " << demangle( factory_type ) << ", got ";
125 if ( info.is_set() )
126 msg << demangle( info.factory.type() ) << " from " << info.library;
127 else
128 msg << "nothing";
129 logger().debug( msg.str() );
130 }
131 }
132
133 Registry::Properties::mapped_type Registry::FactoryInfo::getprop( const Properties::key_type& name ) const {
134 auto p = properties.find( name );
135 return ( p != end( properties ) ) ? p->second : Properties::mapped_type{};
136 }
137
138 Registry::Registry() {}
139
140 void Registry::initialize() {
141 auto _guard = std::scoped_lock{ m_mutex };
142#if defined( __APPLE__ )
143 const auto envVars = { "GAUDI_PLUGIN_PATH", "DYLD_LIBRARY_PATH" };
144 const char sep = ':';
145#else
146 const auto envVars = { "GAUDI_PLUGIN_PATH", "LD_LIBRARY_PATH" };
147 const char sep = ':';
148#endif
149
150 std::regex line_format{ "^(?:[[:space:]]*(?:(v[0-9]+)::)?([^:]+):(.*[^[:space:]]))?[[:space:]]*(?:#.*)?$" };
151 for ( const auto& envVar : envVars ) {
152 std::smatch m;
153 std::stringstream search_path;
154 if ( auto ptr = std::getenv( envVar ) ) search_path << ptr;
155 logger().debug( std::string( "searching factories in " ) + envVar );
156
157 // std::string_view::size_type start_pos = 0, end_pos = 0;
158 std::string dir;
159 while ( std::getline( search_path, dir, sep ) ) {
160 // correctly handle begin of string or path separator
161 logger().debug( " looking into " + dir );
162 // look for files called "*.components" in the directory
163 if ( !fs::is_directory( dir ) ) { continue; }
164 for ( const auto& p : fs::directory_iterator( dir ) ) {
165 if ( p.path().extension() != ".components" || !is_regular_file( p.path() ) ) { continue; }
166 // read the file
167 const auto& fullPath = p.path().string();
168 logger().debug( " reading " + p.path().filename().string() );
169 std::ifstream factories{ fullPath };
170 std::string line;
171 int factoriesCount = 0;
172 int lineCount = 0;
173 while ( !factories.eof() ) {
174 ++lineCount;
175 std::getline( factories, line );
176 if ( regex_match( line, m, line_format ) ) {
177 if ( m[1] != "v2" ) { continue; } // ignore non "v2" and "empty" lines
178 const std::string lib{ m[2] };
179 const std::string fact{ m[3] };
180 m_factories.emplace( fact, FactoryInfo{ lib, {}, { { "ClassName", fact } } } );
181#ifdef GAUDI_REFLEX_COMPONENT_ALIASES
182 // add an alias for the factory using the Reflex convention
183 std::string old_name = old_style_name( fact );
184 if ( fact != old_name ) {
185 m_factories.emplace(
186 old_name, FactoryInfo{ lib, {}, { { "ReflexName", "true" }, { "ClassName", fact } } } );
187 }
188#endif
189 ++factoriesCount;
190 } else {
191 logger().warning( "failed to parse line " + fullPath + ':' + std::to_string( lineCount ) );
192 }
193 }
194 if ( logger().level() <= Logger::Debug ) {
195 logger().debug( " found " + std::to_string( factoriesCount ) + " factories" );
196 }
197 }
198 }
199 }
200 }
201
202 const Registry::FactoryMap& Registry::factories() const {
203 std::call_once( m_initialized, &Registry::initialize, const_cast<Registry*>( this ) );
204 return m_factories;
205 }
206
207 Registry::FactoryMap& Registry::factories() {
208 std::call_once( m_initialized, &Registry::initialize, this );
209 return m_factories;
210 }
211
212 Registry::FactoryInfo& Registry::add( const KeyType& id, FactoryInfo info ) {
213 auto _guard = std::scoped_lock{ m_mutex };
214 FactoryMap& facts = factories();
215
216#ifdef GAUDI_REFLEX_COMPONENT_ALIASES
217 // add an alias for the factory using the Reflex convention
218 const auto old_name = old_style_name( id );
219 if ( id != old_name ) {
220 auto new_info = info;
221
222 new_info.properties["ReflexName"] = "true";
223
224 add( old_name, new_info );
225 }
226#endif
227
228 auto entry = facts.find( id );
229 if ( entry == facts.end() ) {
230 // this factory was not known yet
231 entry = facts.emplace( id, std::move( info ) ).first;
232 } else {
233 // do not replace an existing factory with a new one
234 if ( !entry->second.is_set() ) entry->second = std::move( info );
235 }
236 return entry->second;
237 }
238
239 Registry::FactoryMap::size_type Registry::erase( const KeyType& id ) {
240 auto _guard = std::scoped_lock{ m_mutex };
241 FactoryMap& facts = factories();
242 return facts.erase( id );
243 }
244
245 const Registry::FactoryInfo& Registry::getInfo( const KeyType& id, const bool load ) const {
246 auto _guard = std::scoped_lock{ m_mutex };
247 static const FactoryInfo unknown = { "unknown" };
248 const FactoryMap& facts = factories();
249 auto f = facts.find( id );
250
251 if ( f == facts.end() ) { return unknown; }
252 if ( !load || f->second.is_set() ) { return f->second; }
253
254 if ( !loadPluginLibrary( f->second.library ) ) { return unknown; }
255
256 f = facts.find( id ); // ensure that the iterator is valid
257 return f->second;
258 }
259
260 Registry& Registry::addProperty( const KeyType& id, const KeyType& k, const std::string& v ) {
261 auto _guard = std::scoped_lock{ m_mutex };
262 FactoryMap& facts = factories();
263 auto f = facts.find( id );
264
265 if ( f != facts.end() ) f->second.properties[k] = v;
266 return *this;
267 }
268
269 void Registry::setError( const KeyType& warning ) { m_werror.insert( warning ); }
270
271 void Registry::unsetError( const KeyType& warning ) { m_werror.erase( warning ); }
272
273 std::set<Registry::KeyType> Registry::loadedFactoryNames() const {
274 auto _guard = std::scoped_lock{ m_mutex };
275 std::set<KeyType> l;
276 for ( const auto& f : factories() ) {
277 if ( f.second.is_set() ) l.insert( f.first );
278 }
279 return l;
280 }
281
282 bool Registry::loadPluginLibrary( std::string_view library ) const {
283 // dlopen can not look into GAUDI_PLUGIN_PATH so a search is reimplemented here
284 // and if not found then we fall back to dlopen without full paths
285 // that will look in LD_LIBRARY_PATH
286 std::stringstream ss;
287 ss << getDefaultPluginPath() << ':';
288 if ( auto ptr = std::getenv( "GAUDI_PLUGIN_PATH" ) ) ss << ptr;
289 std::string dir;
290 while ( std::getline( ss, dir, ':' ) ) {
291 if ( !fs::exists( dir ) ) { continue; }
292 const auto path = dir / fs::path( library );
293 if ( is_regular_file( path ) ) {
294 if ( dlopen( path.c_str(), RTLD_LAZY | RTLD_GLOBAL ) ) {
295 return true;
296 } else {
297 logger().warning( "cannot load " + path.string() );
298 if ( char* dlmsg = dlerror() ) { logger().warning( dlmsg ); }
299 }
300 }
301 }
302 // not found yet, let's see if dlopen can find it
303 if ( dlopen( std::string{ library }.c_str(), RTLD_LAZY | RTLD_GLOBAL ) ) {
304 return true;
305 } else {
306 // still no luck
307 logger().warning( "cannot load " + std::string{ library } );
308 if ( char* dlmsg = dlerror() ) { logger().warning( dlmsg ); }
309 }
310 return false;
311 }
312
313 void Logger::report( Level lvl, const std::string& msg ) {
314 static const char* levels[] = { "DEBUG : ", "INFO : ", "WARNING: ", "ERROR : " };
315 if ( lvl >= level() ) { std::cerr << levels[lvl] << msg << std::endl; }
316 }
317
318 static auto s_logger = std::make_unique<Logger>();
319 Logger& logger() { return *s_logger; }
320 void setLogger( Logger* logger ) { s_logger.reset( logger ); }
321
322 // This chunk of code was taken from GaudiKernel (genconf) DsoUtils.h
323 std::string getDSONameFor( void* fptr ) {
324#if defined _GNU_SOURCE || defined __APPLE__
325 Dl_info info;
326 if ( dladdr( fptr, &info ) == 0 ) return "";
327
328 auto pos = std::strrchr( info.dli_fname, '/' );
329 if ( pos )
330 ++pos;
331 else
332 return info.dli_fname;
333 return pos;
334#else
335 return "";
336#endif
337 }
338
339 std::string getDefaultPluginPath() {
340#ifdef PLUGIN_PATH_RELATIVE
341# define stringify( x ) stringify_( x )
342# define stringify_( x ) #x
343 std::string relative_path = stringify( PLUGIN_PATH_RELATIVE );
344#else
345 std::string relative_path = ".";
346#endif
347 Dl_info info;
348 if ( dladdr( (void*)_dso_marker, &info ) != 0 ) {
349 auto plugin_path = fs::path( info.dli_fname ).parent_path() / relative_path;
350 return plugin_path.string();
351 } else {
352 return relative_path;
353 }
354 }
355 } // namespace Details
356
357 void SetDebug( int debugLevel ) {
358 using namespace Details;
359 Logger& l = logger();
360 if ( debugLevel > 1 )
361 l.setLevel( Logger::Debug );
362 else if ( debugLevel > 0 )
363 l.setLevel( Logger::Info );
364 else
365 l.setLevel( Logger::Warning );
366 }
367
368 int Debug() {
369 using namespace Details;
370 switch ( logger().level() ) {
371 case Logger::Debug:
372 return 2;
373 case Logger::Info:
374 return 1;
375 default:
376 return 0;
377 }
378 }
379 }
380 } // namespace PluginService
381} // namespace Gaudi
#define GAUDI_PLUGIN_SERVICE_V2_INLINE
std::string demangle(const std::string &id)
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