The Gaudi Framework  master (d98a2936)
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( __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
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:324
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:304
Gaudi::Units::m
constexpr double m
Definition: SystemOfUnits.h:107
GaudiPartProp.tests.v2
v2
Definition: tests.py:59
Gaudi::PluginService::v2::Details::getDSONameFor
std::string getDSONameFor(void *fptr)
Definition: PluginServiceV2.cpp:307
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:303
Gaudi::PluginService::v2::Debug
int Debug()
Definition: PluginServiceV2.cpp:335
MSG::Level
Level
Definition: IMessageSvc.h:22
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