Belle II Software light-2406-ragdoll
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
15import os
16import re
17from SCons.Builder import Builder
18from SCons.Scanner.C import CScanner
19
20# Classes outside of namespace Belle2.
21# Such classes can be linked using comment "// global".
22linkdef_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::.*
26linkdef_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
30linkdef_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)
34linkdef_implicit = \
35 re.compile(r'^#pragma\s+link\s+C\+\+\s+[\w]*\s+Belle2::.*;\s*//.*implicit.*$', re.M)
36
37
38def 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
114rootcling = 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())
119rootcling.action.cmdstr = '${ROOTCLINGCOMSTR}'
120
121# define builder for class version check
122classversion = Builder(action='b2code-classversion-check --error-style gcc $SOURCE && touch $TARGET')
123classversion.action.cmdstr = '${CLASSVERSIONCOMSTR}'
124
125
126def generate(env):
127 env['BUILDERS']['RootDict'] = rootcling
128 env['BUILDERS']['ClassVersionCheck'] = classversion
129
130
131def exists(env):
132 return True