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