Loading [MathJax]/extensions/tex2jax.js
The Gaudi Framework  master (42b00024)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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
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>
37 namespace fs = boost::filesystem;
38 #else
39 # include <filesystem>
40 namespace fs = std::filesystem;
41 #endif // USE_BOOST_FILESYSTEM
42 
43 namespace {
44  std::mutex registrySingletonMutex;
45 }
46 
47 #include <algorithm>
48 
49 namespace {
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 
83 namespace 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( _WIN32 )
139  const auto envVars = { "PATH" };
140  const char sep = ';';
141 #elif defined( __APPLE__ )
142  const auto envVars = { "GAUDI_PLUGIN_PATH" };
143  const char sep = ':';
144 #else
145  const auto envVars = { "GAUDI_PLUGIN_PATH", "LD_LIBRARY_PATH" };
146  const char sep = ':';
147 #endif
148 
149  std::regex line_format{ "^(?:[[:space:]]*(?:(v[0-9]+)::)?([^:]+):(.*[^[:space:]]))?[[:space:]]*(?:#.*)?$" };
150  for ( const auto& envVar : envVars ) {
151  std::smatch m;
152  std::stringstream search_path;
153  if ( auto ptr = std::getenv( envVar ) ) search_path << ptr;
154  logger().debug( std::string( "searching factories in " ) + envVar );
155 
156  // std::string_view::size_type start_pos = 0, end_pos = 0;
157  std::string dir;
158  while ( std::getline( search_path, dir, sep ) ) {
159  // correctly handle begin of string or path separator
160  logger().debug( " looking into " + dir );
161  // look for files called "*.components" in the directory
162  if ( !fs::is_directory( dir ) ) { continue; }
163  for ( const auto& p : fs::directory_iterator( dir ) ) {
164  if ( p.path().extension() != ".components" || !is_regular_file( p.path() ) ) { continue; }
165  // read the file
166  const auto& fullPath = p.path().string();
167  logger().debug( " reading " + p.path().filename().string() );
168  std::ifstream factories{ fullPath };
169  std::string line;
170  int factoriesCount = 0;
171  int lineCount = 0;
172  while ( !factories.eof() ) {
173  ++lineCount;
174  std::getline( factories, line );
175  if ( regex_match( line, m, line_format ) ) {
176  if ( m[1] != "v2" ) { continue; } // ignore non "v2" and "empty" lines
177  const std::string lib{ m[2] };
178  const std::string fact{ m[3] };
179  m_factories.emplace( fact, FactoryInfo{ lib, {}, { { "ClassName", fact } } } );
180 #ifdef GAUDI_REFLEX_COMPONENT_ALIASES
181  // add an alias for the factory using the Reflex convention
182  std::string old_name = old_style_name( fact );
183  if ( fact != old_name ) {
184  m_factories.emplace(
185  old_name, FactoryInfo{ lib, {}, { { "ReflexName", "true" }, { "ClassName", fact } } } );
186  }
187 #endif
188  ++factoriesCount;
189  } else {
190  logger().warning( "failed to parse line " + fullPath + ':' + std::to_string( lineCount ) );
191  }
192  }
193  if ( logger().level() <= Logger::Debug ) {
194  logger().debug( " found " + std::to_string( factoriesCount ) + " factories" );
195  }
196  }
197  }
198  }
199  }
200 
201  const Registry::FactoryMap& Registry::factories() const {
202  std::call_once( m_initialized, &Registry::initialize, const_cast<Registry*>( this ) );
203  return m_factories;
204  }
205 
206  Registry::FactoryMap& Registry::factories() {
207  std::call_once( m_initialized, &Registry::initialize, this );
208  return m_factories;
209  }
210 
211  Registry::FactoryInfo& Registry::add( const KeyType& id, FactoryInfo info ) {
212  auto _guard = std::scoped_lock{ m_mutex };
213  FactoryMap& facts = factories();
214 
215 #ifdef GAUDI_REFLEX_COMPONENT_ALIASES
216  // add an alias for the factory using the Reflex convention
217  const auto old_name = old_style_name( id );
218  if ( id != old_name ) {
219  auto new_info = info;
220 
221  new_info.properties["ReflexName"] = "true";
222 
223  add( old_name, new_info );
224  }
225 #endif
226 
227  auto entry = facts.find( id );
228  if ( entry == facts.end() ) {
229  // this factory was not known yet
230  entry = facts.emplace( id, std::move( info ) ).first;
231  } else {
232  // do not replace an existing factory with a new one
233  if ( !entry->second.is_set() ) entry->second = std::move( info );
234  }
235  return entry->second;
236  }
237 
238  Registry::FactoryMap::size_type Registry::erase( const KeyType& id ) {
239  auto _guard = std::scoped_lock{ m_mutex };
240  FactoryMap& facts = factories();
241  return facts.erase( id );
242  }
243 
244  const Registry::FactoryInfo& Registry::getInfo( const KeyType& id, const bool load ) const {
245  auto _guard = std::scoped_lock{ m_mutex };
246  static const FactoryInfo unknown = { "unknown" };
247  const FactoryMap& facts = factories();
248  auto f = facts.find( id );
249 
250  if ( f == facts.end() ) { return unknown; }
251  if ( !load || f->second.is_set() ) { return f->second; }
252  const std::string_view library = f->second.library;
253 
254  // dlopen can not look into GAUDI_PLUGIN_PATH so a search is reimplemented here
255  // and if not found then we fall back to dlopen without full paths
256  // that will look in LD_LIBRARY_PATH
257  std::stringstream ss;
258  if ( auto ptr = std::getenv( "GAUDI_PLUGIN_PATH" ) ) ss << ptr;
259  std::string dir;
260  bool found = false;
261  while ( std::getline( ss, dir, ':' ) && !found ) {
262  if ( !fs::exists( dir ) ) { continue; }
263  if ( is_regular_file( dir / fs::path( library ) ) ) {
264  // logger().debug( "found " + dirName.string() + "/" + library.c_str() + " for factory " + id );
265  if ( !tryDLOpen( ( dir / fs::path( library ) ).string() ) ) {
266  return unknown;
267  } else {
268  found = true;
269  break;
270  }
271  }
272  }
273  if ( !found && !tryDLOpen( library ) ) return unknown;
274  f = facts.find( id ); // ensure that the iterator is valid
275  return f->second;
276  }
277 
278  Registry& Registry::addProperty( const KeyType& id, const KeyType& k, const std::string& v ) {
279  auto _guard = std::scoped_lock{ m_mutex };
280  FactoryMap& facts = factories();
281  auto f = facts.find( id );
282 
283  if ( f != facts.end() ) f->second.properties[k] = v;
284  return *this;
285  }
286 
287  void Registry::setError( const KeyType& warning ) { m_werror.insert( warning ); }
288 
289  void Registry::unsetError( const KeyType& warning ) { m_werror.erase( warning ); }
290 
291  std::set<Registry::KeyType> Registry::loadedFactoryNames() const {
292  auto _guard = std::scoped_lock{ m_mutex };
293  std::set<KeyType> l;
294  for ( const auto& f : factories() ) {
295  if ( f.second.is_set() ) l.insert( f.first );
296  }
297  return l;
298  }
299 
300  void Logger::report( Level lvl, const std::string& msg ) {
301  static const char* levels[] = { "DEBUG : ", "INFO : ", "WARNING: ", "ERROR : " };
302  if ( lvl >= level() ) { std::cerr << levels[lvl] << msg << std::endl; }
303  }
304 
305  static auto s_logger = std::make_unique<Logger>();
306  Logger& logger() { return *s_logger; }
307  void setLogger( Logger* logger ) { s_logger.reset( logger ); }
308 
309  // This chunk of code was taken from GaudiKernel (genconf) DsoUtils.h
310  std::string getDSONameFor( void* fptr ) {
311 #if defined _GNU_SOURCE || defined __APPLE__
312  Dl_info info;
313  if ( dladdr( fptr, &info ) == 0 ) return "";
314 
315  auto pos = std::strrchr( info.dli_fname, '/' );
316  if ( pos )
317  ++pos;
318  else
319  return info.dli_fname;
320  return pos;
321 #else
322  return "";
323 #endif
324  }
325  } // namespace Details
326 
327  void SetDebug( int debugLevel ) {
328  using namespace Details;
329  Logger& l = logger();
330  if ( debugLevel > 1 )
331  l.setLevel( Logger::Debug );
332  else if ( debugLevel > 0 )
333  l.setLevel( Logger::Info );
334  else
335  l.setLevel( Logger::Warning );
336  }
337 
338  int Debug() {
339  using namespace Details;
340  switch ( logger().level() ) {
341  case Logger::Debug:
342  return 2;
343  case Logger::Info:
344  return 1;
345  default:
346  return 0;
347  }
348  }
349  }
350  } // namespace PluginService
351 } // namespace Gaudi
Gaudi::PluginService::v2::Details::reportBadAnyCast
void reportBadAnyCast(const std::type_info &factory_type, const std::string &id)
Definition: PluginServiceV2.cpp:116
AtlasMCRecoFullPrecedenceDump.path
path
Definition: AtlasMCRecoFullPrecedenceDump.py:49
Gaudi::PluginService::v2::SetDebug
void SetDebug(int debugLevel)
Definition: PluginServiceV2.cpp:327
GAUDI_PLUGIN_SERVICE_V2_INLINE
#define GAUDI_PLUGIN_SERVICE_V2_INLINE
Definition: PluginServiceCommon.h:17
GaudiMP.FdsRegistry.msg
msg
Definition: FdsRegistry.py:19
gaudirun.c
c
Definition: gaudirun.py:525
GaudiPartProp.tests.id
id
Definition: tests.py:111
PluginService.h
Gaudi::cxx::for_each
void for_each(ContainerOfSynced &c, Fun &&f)
Definition: SynchronizedValue.h:98
Gaudi::PluginService::v2::Details::setLogger
void setLogger(Logger *logger)
Definition: PluginServiceV2.cpp:307
Gaudi::Units::m
constexpr double m
Definition: SystemOfUnits.h:108
GaudiPartProp.tests.v2
v2
Definition: tests.py:59
Gaudi::PluginService::v2::Details::getDSONameFor
std::string getDSONameFor(void *fptr)
Definition: PluginServiceV2.cpp:310
cpluginsvc.factories
def factories()
Definition: cpluginsvc.py:93
gaudirun.level
level
Definition: gaudirun.py:364
Gaudi
This file provides a Grammar for the type Gaudi::Accumulators::Axis It allows to use that type from p...
Definition: __init__.py:1
gaudiComponentHelp.properties
properties
Definition: gaudiComponentHelp.py:68
Gaudi::PluginService::v2::Details::logger
Logger & logger()
Definition: PluginServiceV2.cpp:306
Gaudi::PluginService::v2::Debug
int Debug()
Definition: PluginServiceV2.cpp:338
MSG::Level
Level
Definition: IMessageSvc.h:25
ConditionsStallTest.name
name
Definition: ConditionsStallTest.py:77
gaudirun.l
dictionary l
Definition: gaudirun.py:583
plotSpeedupsPyRoot.line
line
Definition: plotSpeedupsPyRoot.py:198
Properties.v
v
Definition: Properties.py:122
IOTest.end
end
Definition: IOTest.py:125
GaudiPython.Persistency.add
def add(instance)
Definition: Persistency.py:50
Gaudi::PluginService::v2::Details::demangle
std::string demangle(const std::string &id)
Definition: PluginServiceV2.cpp:87