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