Belle II Software  release-05-01-25
root_dict.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 # Custom builder for the Root dictionaries.
5 # It will also generate the .rootmap files to allow ROOT
6 # to auto-load libraries
7 
8 import os
9 import re
10 from SCons.Builder import Builder
11 from SCons.Scanner.C import CScanner
12 
13 # Classes outside of namespace Belle2.
14 # Such classes can be linked using comment "// global".
15 linkdef_global = \
16  re.compile(r'^#pragma\s+link\s+C\+\+\s+[\w]*\s+([\w]*).*[+-]?\!?;\s*//.*global.*$', re.M)
17 
18 # everything that has #pragma link C++ .* Belle2::.*
19 linkdef_everything = \
20  re.compile(r'^#pragma\s+link\s+C\+\+\s+[\w]*\s+Belle2::.*$', re.M)
21 
22 # regular expression to find class names in linkdef files
23 linkdef_class_re = \
24  re.compile(r'^#pragma\s+link\s+C\+\+\s+[\w]*\s+Belle2::([\w]*::)?([\w]*).*[+-]?\!?;.*$', re.M)
25 
26 # link requests with '// implicit' comment (doesn't require header file)
27 linkdef_implicit = \
28  re.compile(r'^#pragma\s+link\s+C\+\+\s+[\w]*\s+Belle2::.*;\s*//\s*implicit\s*$', re.M)
29 
30 
31 def linkdef_emitter(target, source, env):
32  linkdef = source.pop()
33  # determine the right include directory
34  source_dir = os.path.dirname(str(linkdef))
35  include_dir = source_dir
36  if include_dir.endswith('include'):
37  include_dir = os.path.dirname(include_dir)
38  include_dir = os.path.join(env['INCDIR'], include_dir)
39 
40  dict_basename = os.path.splitext(str(target[0]))[0]
41  # add rootmap file as extra target
42  target.append(dict_basename + '.rootmap')
43  # add root pcm file as extra target
44  target.append(dict_basename + '_rdict.pcm')
45 
46  # loop over class names and construct the corresponding header file names
47  contents = linkdef.get_text_contents()
48  for line in contents.split('\n'):
49  # Classes outside of namespace Belle2.
50  match = linkdef_global.match(line)
51  if match is not None:
52  classname = match.group(1)
53  if classname is not None:
54  include_base = classname + '.h'
55  header_file = os.path.join(source_dir, include_base)
56  if os.path.isfile(header_file):
57  include_file = os.path.join(include_dir, include_base)
58  if include_file not in source:
59  source.append(include_file)
60  else:
61  print(f'Cannot find header file for the line "{line}".')
62 
63  # first check if this is looks like an actual request to create a dictionary
64  if linkdef_everything.search(line) is None:
65  continue
66 
67  match = linkdef_class_re.search(line)
68  if match is None:
69  raise RuntimeError(
70  "%s contains '%s' which we couldn't parse. The syntax may be incorrect," +
71  " or the build system may lack support for the feature you're using." %
72  (str(linkdef), line))
73 
74  namespace = match.group(1) # or possibly a class, but it might match the header file
75  classname = match.group(2)
76  if not classname:
77  raise RuntimeError("%s contains '%s' without class name?" % (str(linkdef), str(line)))
78 
79  is_implicit = not linkdef_implicit.search(line) is None
80  if is_implicit:
81  continue # do not look for a header for this request
82 
83  include_base = classname + '.h'
84  header_file = os.path.join(source_dir, include_base)
85 
86  if not os.path.isfile(header_file):
87  if not namespace:
88  # no //implicit for suppression found...
89  print("%s contains '%s' where we couldn't find a header file. "
90  "If dictionary compilation fails, this might be the reason. "
91  "For classes residing in other directories and already "
92  "included via other link requests, add '// implicit' at "
93  "the end to suppress this message." % (str(linkdef), str(line)))
94  continue
95 
96  # remove trailing '::', add '.h'
97  include_base = namespace.split(':')[0] + '.h'
98  include_file = os.path.join(include_dir, include_base)
99  if include_file not in source:
100  source.append(include_file)
101 
102  # make sure linkdef is last source because that's how rootcling wants it
103  source.append(linkdef)
104  return (target, source)
105 
106 
107 # define builder for root dictionaries
108 rootcling = Builder(action='rootcling -f $TARGET $CLINGFLAGS -rmf "${TARGET.base}.rootmap" -rml lib${ROOTCLING_ROOTMAP_LIB}.so '
109  '$_CPPDEFFLAGS $_CPPINCFLAGS $SOURCES', emitter=linkdef_emitter, source_scanner=CScanner())
110 rootcling.action.cmdstr = '${ROOTCLINGCOMSTR}'
111 
112 
113 def generate(env):
114  env['BUILDERS']['RootDict'] = rootcling
115 
116 
117 def exists(env):
118  return True