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