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