cmake.py
Go to the documentation of this file.
1 import collections
2 import re
3 
4 VARIABLE_PATTERN = re.compile(r'\$\{([^\}]+)\}')
5 QUOTED_PATTERN = re.compile(r'"([^"]+)"')
6 
7 BUILD_TARGET_COMMANDS = ['add_library', 'add_executable', 'add_rostest',
8  'target_include_directories', 'add_dependencies', 'target_link_libraries',
9  'set_target_properties', 'ament_target_dependencies']
10 TEST_COMMANDS = [('group', 'CATKIN_ENABLE_TESTING'), 'catkin_download_test_data',
11  'roslint_cpp', 'roslint_python', 'roslint_add_test',
12  'catkin_add_nosetests', 'catkin_add_gtest', 'add_rostest_gtest']
13 INSTALL_COMMANDS = ['install', 'catkin_install_python']
14 
15 BASE_ORDERING = ['cmake_minimum_required', 'project', 'set_directory_properties', 'find_package', 'pkg_check_modules',
16  'set', 'catkin_generate_virtualenv', 'catkin_python_setup', 'add_definitions',
17  'add_message_files', 'add_service_files', 'add_action_files', 'rosidl_generate_interfaces',
18  'generate_dynamic_reconfigure_options', 'generate_messages', 'catkin_package', 'catkin_metapackage',
19  BUILD_TARGET_COMMANDS + ['include_directories'],
20  'ament_target_dependencies', 'ament_export_include_directories', 'ament_export_libraries',
21  'ament_export_dependencies',
22  'ament_package']
23 
24 
25 def get_style(cmake):
26  """Examine the contents of the cmake parameter and determine the style.
27 
28  There are four possible styles:
29  1) test_first (where test commands come strictly before install commands)
30  2) install_first (where test commands come strictly after install commands)
31  3) mixed (where test and install commands are not clearly delineated)
32  4) None (where there are only install commands, or only test commands, or neither)
33  """
34  cats = []
35  for content in cmake.contents:
36  cat = None
37  if isinstance(content, CommandGroup) and is_testing_group(content):
38  cat = 'test'
39  elif isinstance(content, Command):
40  if content.command_name in TEST_COMMANDS:
41  cat = 'test'
42  elif content.command_name in INSTALL_COMMANDS:
43  cat = 'install'
44  if cat is None:
45  continue
46 
47  if len(cats) == 0 or cats[-1] != cat:
48  cats.append(cat)
49 
50  if len(cats) > 2:
51  return 'mixed'
52  if len(cats) < 2:
53  return None
54  first_cat = cats[0]
55  return first_cat + '_first'
56 
57 
58 def get_ordering(style):
59  """Given the style, return the correct ordering."""
60  if style == 'install_first':
61  return BASE_ORDERING + INSTALL_COMMANDS + TEST_COMMANDS
62  else:
63  return BASE_ORDERING + TEST_COMMANDS + INSTALL_COMMANDS
64 
65 
66 def get_ordering_index(command_name, ordering):
67  """
68  Given a command name, determine the integer index into the ordering.
69 
70  The ordering is a list of strings and arrays of strings.
71 
72  If the command name matches one of the strings in the inner arrays,
73  the index of the inner array is returned.
74 
75  If the command name matches one of the other strings, its index is returned.
76 
77  Otherwise, the length of the ordering is returned (putting non-matches at the end)
78  """
79  for i, o in enumerate(ordering):
80  if type(o) == list:
81  if command_name in o:
82  return i
83  elif command_name == o:
84  return i
85  if command_name:
86  print('\tUnsure of ordering for ' + str(command_name))
87  return len(ordering)
88 
89 
90 def get_sort_key(content, anchors, ordering):
91  """
92  Given a piece of cmake content, return a tuple representing its sort_key.
93 
94  The first element of the tuple is the ordering_index of the content.
95  The second element is an additional variable used for sorting among elements with the same ordering_index
96 
97  Most notably, we want all build commands with a particular library/executable to be grouped together.
98  In that case, we use the anchors parameter, which is an ordered list of all the library/executables in the file.
99  Then, the second variable is a tuple itself, with the first element being the index of library/executable in the
100  anchors list, and the second is an integer representing the canonical order of the build commands.
101  """
102  if content is None:
103  return len(ordering) + 1, None
104  index = None
105  key = ()
106  if content.__class__ == CommandGroup:
107  key_token = ()
108  for token in content.initial_tag.get_tokens(include_name=True):
109  if token == 'NOT':
110  continue
111  key_token = token
112  break
113  index = get_ordering_index(('group', key_token), ordering)
114  else: # Command
115  index = get_ordering_index(content.command_name, ordering)
116  if content.command_name in BUILD_TARGET_COMMANDS:
117  token = content.first_token()
118  if token not in anchors:
119  anchors.append(token)
120  key = anchors.index(token), BUILD_TARGET_COMMANDS.index(content.command_name)
121  elif content.command_name == 'include_directories' and 'include_directories' in anchors:
122  key = -1, anchors.index('include_directories')
123  return index, key
124 
125 
127  def __init__(self, prename='', name_val_sep=' ', val_sep=' '):
128  self.prename = prename
129  self.name_val_sep = name_val_sep
130  self.val_sep = val_sep
131 
132  def __repr__(self):
133  return 'SectionStyle(%s, %s, %s)' % (repr(self.prename), repr(self.name_val_sep), repr(self.val_sep))
134 
135 
136 class Section:
137  def __init__(self, name='', values=None, style=None):
138  self.name = name
139  if values is None:
140  self.values = []
141  else:
142  self.values = list(values)
143  if style:
144  self.style = style
145  else:
146  self.style = SectionStyle()
147 
148  def add(self, v):
149  self.values.append(v)
150 
151  def add_values(self, new_values, alpha_order=True):
152  """Add the new_values to the values.
153 
154  If alpha_order is true AND the existing values are already alphabetized,
155  add the new values in alphabetical order.
156  """
157  # Check if existing values are sorted
158  if alpha_order and self.values == sorted(self.values):
159  all_values = self.values + list(new_values)
160  self.values = sorted(all_values)
161  else:
162  self.values += sorted(new_values)
163 
164  def is_valid(self):
165  return len(self.name) > 0 or len(self.values) > 0
166 
167  def __repr__(self):
168  s = self.style.prename
169  if len(self.name) > 0:
170  s += self.name
171  if len(self.values) > 0 or '\n' in self.style.name_val_sep:
172  s += self.style.name_val_sep
173  s += self.style.val_sep.join(self.values)
174  return s
175 
176 
177 class Command:
178  def __init__(self, command_name):
179  self.command_name = command_name
180  self.original = None
181  self.changed = False
182  self.pre_paren = ''
183  self.sections = []
184 
185  def get_real_sections(self):
186  return [s for s in self.sections if type(s) != str]
187 
188  def get_section(self, key):
189  for s in self.get_real_sections():
190  if s.name == key:
191  return s
192  return None
193 
194  def get_sections(self, key):
195  return [s for s in self.get_real_sections() if s.name == key]
196 
197  def add_section(self, key, values=None, style=None):
198  self.sections.append(Section(key, values, style))
199  self.changed = True
200 
201  def add(self, section):
202  if section:
203  self.sections.append(section)
204  self.changed = True
205 
206  def first_token(self):
207  return self.get_real_sections()[0].values[0]
208 
209  def remove_sections(self, key):
210  bad_sections = self.get_sections(key)
211  if not bad_sections:
212  return
213  self.changed = True
214  self.sections = [section for section in self.sections if section not in bad_sections]
215  if len(self.sections) == 1 and type(self.sections[0]) == str:
216  self.sections = []
217 
218  def get_tokens(self, include_name=False):
219  tokens = []
220  for section in self.get_real_sections():
221  if include_name and section.name:
222  tokens.append(section.name)
223  tokens += section.values
224  return tokens
225 
226  def add_token(self, s):
227  sections = self.get_real_sections()
228  if len(sections) == 0:
229  self.add(Section(values=[s]))
230  else:
231  last = sections[-1]
232  last.values.append(s)
233  self.changed = True
234 
235  def __repr__(self):
236  if self.original and not self.changed:
237  return self.original
238 
239  s = self.command_name + self.pre_paren + '('
240  for section in map(str, self.sections):
241  if s[-1] not in '( \n' and section[0] not in ' \n':
242  s += ' '
243  s += section
244  if '\n' in s and s[-1] != '\n':
245  s += '\n'
246  s += ')'
247  return s
248 
249 
251  def __init__(self, initial_tag, sub, close_tag):
252  self.initial_tag = initial_tag
253  self.sub = sub
254  self.close_tag = close_tag
255 
256  def __repr__(self):
257  return str(self.initial_tag) + str(self.sub) + str(self.close_tag)
258 
259 
260 def is_testing_group(content):
261  cmd = content.initial_tag
262  return cmd.command_name == 'if' and cmd.sections and cmd.sections[0].name == 'CATKIN_ENABLE_TESTING'
263 
264 
265 class CMake:
266  def __init__(self, file_path=None, initial_contents=None, depth=0):
267  self.file_path = file_path
268  if initial_contents is None:
269  self.contents = []
270  else:
271  self.contents = initial_contents
272  self.content_map = collections.defaultdict(list)
273  for content in self.contents:
274  if content.__class__ == Command:
275  self.content_map[content.command_name].append(content)
276  elif content.__class__ == CommandGroup:
277  self.content_map['group'].append(content)
278  self.depth = depth
279 
280  self.variables = {}
281  for cmd in self.content_map['set']:
282  tokens = cmd.get_tokens(include_name=True)
283  self.variables[tokens[0]] = ' '.join(tokens[1:])
284  self.variables['PROJECT_NAME'] = self.get_project_name()
285 
287 
288  def get_project_name(self):
289  project_tags = self.content_map['project']
290  if not project_tags:
291  return ''
292  # Get all tokens just in case the name is all caps
293  return project_tags[0].get_tokens(include_name=True)[0]
294 
295  def resolve_variables(self, var):
296  if type(var) == str:
297  s = var
298  m = VARIABLE_PATTERN.search(s)
299  if not m:
300  return s
301 
302  for k, v in self.variables.items():
303  s = s.replace('${%s}' % k, v)
304  return s
305  else:
306  tokens = []
307  for token in var:
308  if token and token[0] == '#':
309  continue
310  m = QUOTED_PATTERN.match(token)
311  if m:
312  token = m.group(1)
313  token = self.resolve_variables(token)
314  tokens += token.split(' ')
315  return tokens
316 
317  def get_resolved_tokens(self, cmd, include_name=False):
318  return self.resolve_variables(cmd.get_tokens(include_name))
319 
320  def get_insertion_index(self, cmd):
321  anchors = self.get_ordered_build_targets()
322  ordering = get_ordering(self.get_desired_style())
323 
324  new_key = get_sort_key(cmd, anchors, ordering)
325  i_index = 0
326 
327  for i, content in enumerate(self.contents):
328  if type(content) == str:
329  continue
330  key = get_sort_key(content, anchors, ordering)
331  if key <= new_key:
332  i_index = i + 1
333  elif key[0] != len(ordering):
334  return i_index
335  return len(self.contents)
336 
337  def add_command(self, cmd):
338  i_index = self.get_insertion_index(cmd)
339  sub_contents = []
340  if i_index > 0 and type(self.contents[i_index - 1]) != str:
341  sub_contents.append('\n')
342  if self.depth > 0:
343  sub_contents.append(' ' * self.depth)
344  sub_contents.append(cmd)
345  sub_contents.append('\n')
346  else:
347  sub_contents.append(cmd)
348  if i_index == len(self.contents):
349  sub_contents.append('\n')
350 
351  self.contents = self.contents[:i_index] + sub_contents + self.contents[i_index:]
352 
353  if cmd.__class__ == Command:
354  self.content_map[cmd.command_name].append(cmd)
355  elif cmd.__class__ == CommandGroup:
356  self.content_map['group'].append(cmd)
357 
358  def remove_command(self, cmd):
359  print('\tRemoving %s' % str(cmd).replace('\n', ' ').replace(' ', ''))
360  self.contents.remove(cmd)
361  self.content_map[cmd.command_name].remove(cmd)
362 
363  def remove_all_commands(self, cmd_name):
364  cmds = list(self.content_map[cmd_name])
365  for cmd in cmds:
366  self.remove_command(cmd)
367 
368  def is_metapackage(self):
369  return len(self.content_map['catkin_metapackage']) > 0
370 
371  def get_source_build_rules(self, tag, resolve_target_name=False):
372  rules = {}
373  for cmd in self.content_map[tag]:
374  resolved_tokens = self.get_resolved_tokens(cmd, True)
375 
376  if resolve_target_name:
377  target = resolved_tokens[0]
378  else:
379  tokens = cmd.get_tokens(True)
380  target = tokens[0]
381 
382  deps = resolved_tokens[1:]
383  rules[target] = deps
384  return rules
385 
386  def get_source_helper(self, tag):
387  lib_src = set()
388  for deps in self.get_source_build_rules(tag).values():
389  lib_src.update(deps)
390  return lib_src
391 
393  return self.get_source_helper('add_library')
394 
396  return self.get_source_helper('add_executable')
397 
398  def get_libraries(self):
399  return list(self.get_source_build_rules('add_library').keys())
400 
401  def get_executables(self):
402  return list(self.get_source_build_rules('add_executable').keys())
403 
405  targets = {}
406  targets.update(self.get_source_build_rules('add_library'))
407  targets.update(self.get_source_build_rules('add_executable'))
408  return targets
409 
411  targets = []
412  for content in self.contents:
413  if content.__class__ != Command:
414  continue
415  if content.command_name == 'include_directories':
416  targets.append('include_directories')
417  continue
418  elif content.command_name not in BUILD_TARGET_COMMANDS:
419  continue
420  token = content.first_token()
421  if token not in targets:
422  targets.append(token)
423  return targets
424 
425  def get_test_sections(self):
426  sections = []
427  for content in self.content_map['group']:
428  if is_testing_group(content):
429  sections.append(content.sub)
430  return sections
431 
432  def get_test_source(self):
433  test_files = set()
434  for sub in self.get_test_sections():
435  test_files.update(sub.get_library_source())
436  test_files.update(sub.get_executable_source())
437  return test_files
438 
439  def get_test_section(self, create_if_needed=False):
440  sections = self.get_test_sections()
441  if len(sections) > 0:
442  return sections[0]
443  if not create_if_needed:
444  return None
445 
446  # Create Test Section
447  initial_cmd = Command('if')
448  initial_cmd.add_section('CATKIN_ENABLE_TESTING')
449 
450  test_contents = CMake(initial_contents=['\n'], depth=self.depth + 1)
451 
452  final_cmd = Command('endif')
453 
454  cg = CommandGroup(initial_cmd, test_contents, final_cmd)
455  self.add_command(cg)
456  return cg.sub
457 
458  def get_command_section(self, command_name, section_name):
459  """Return the first command that matches the command name and has a matching section name.
460 
461  If the section name is not found, return a command with the matching command name
462  """
463  if len(self.content_map[command_name]) == 0:
464  return None, None
465  for cmd in self.content_map[command_name]:
466  s = cmd.get_section(section_name)
467  if s:
468  return cmd, s
469  return self.content_map[command_name][0], None
470 
471  def section_check(self, items, cmd_name, section_name='', zero_okay=False, alpha_order=True):
472  """Ensure there's a CMake command of the given type with the given section name and items."""
473  if len(items) == 0 and not zero_okay:
474  return
475 
476  cmd, section = self.get_command_section(cmd_name, section_name)
477 
478  if cmd is None:
479  cmd = Command(cmd_name)
480  self.add_command(cmd)
481 
482  if section is None:
483  cmd.add_section(section_name, sorted(items))
484  else:
485  existing = self.resolve_variables(section.values)
486  needed_items = [item for item in items if item not in existing and item not in section.values]
487  if needed_items:
488  section.add_values(needed_items, alpha_order)
489  cmd.changed = True
490 
491  def get_clusters(self, desired_style):
492  """Return a list of clusters where each cluster is an array of strings with a Command/CommandGroup at the end.
493 
494  The clusters are sorted according to the desired style.
495  The strings are grouped at the beginning to maintain the newlines and indenting before each Command.
496  """
497  anchors = self.get_ordered_build_targets()
498  ordering = get_ordering(desired_style)
499  clusters = []
500  current = []
501  for content in self.contents:
502  current.append(content)
503  if type(content) == str:
504  continue
505  key = get_sort_key(content, anchors, ordering)
506  clusters.append((key, current))
507  current = []
508  if len(current) > 0:
509  clusters.append((get_sort_key(None, anchors, ordering), current))
510 
511  return [kv[1] for kv in sorted(clusters, key=lambda kv: kv[0])]
512 
513  def get_desired_style(self, default_style=None):
514  """Determine which style to use, install_first or test_first.
515 
516  If the default style is one of those two, use it
517  """
518  if default_style in ['install_first', 'test_first']:
519  desired_style = default_style
520  elif default_style is not None:
521  raise RuntimeError('Configured default cmake style "{}"'
522  ' is not install_first or test_first'.format(default_style))
523  elif self.existing_style in ['install_first', 'test_first']:
524  desired_style = self.existing_style
525  else:
526  # Otherwise, do test first
527  desired_style = 'test_first'
528 
529  return desired_style
530 
531  def enforce_ordering(self, default_style=None):
532  desired_style = self.get_desired_style(default_style)
533  clusters = self.get_clusters(desired_style)
534  self.contents = []
535  for contents in clusters:
536  self.contents += contents
537 
538  for group in self.content_map['group']:
539  group.sub.enforce_ordering(default_style)
540 
541  def upgrade_minimum_version(self, new_version):
542  """Upgrade the CMake version to the new version (specified as a tuple)."""
543  for cmd in self.content_map['cmake_minimum_required']:
544  section = cmd.get_section('VERSION')
545  version = tuple(map(int, section.values[0].split('.')))
546  if version < new_version:
547  section.values[0] = '.'.join(map(str, new_version))
548  cmd.changed = True
549 
550  def __repr__(self):
551  return ''.join(map(str, self.contents))
552 
553  def write(self, fn=None):
554  if fn is None:
555  fn = self.file_path
556  with open(fn, 'w') as cmake:
557  cmake.write(str(self))
def get_desired_style(self, default_style=None)
Definition: cmake.py:513
def get_resolved_tokens(self, cmd, include_name=False)
Definition: cmake.py:317
def enforce_ordering(self, default_style=None)
Definition: cmake.py:531
def get_ordered_build_targets(self)
Definition: cmake.py:410
def remove_sections(self, key)
Definition: cmake.py:209
def __init__(self, file_path=None, initial_contents=None, depth=0)
Definition: cmake.py:266
def __init__(self, prename='', name_val_sep=' ', val_sep=' ')
Definition: cmake.py:127
def write(self, fn=None)
Definition: cmake.py:553
def remove_all_commands(self, cmd_name)
Definition: cmake.py:363
def get_source_helper(self, tag)
Definition: cmake.py:386
def get_target_build_rules(self)
Definition: cmake.py:404
def __init__(self, command_name)
Definition: cmake.py:178
def __init__(self, initial_tag, sub, close_tag)
Definition: cmake.py:251
def get_ordering_index(command_name, ordering)
Definition: cmake.py:66
def get_clusters(self, desired_style)
Definition: cmake.py:491
def get_tokens(self, include_name=False)
Definition: cmake.py:218
def add_section(self, key, values=None, style=None)
Definition: cmake.py:197
def is_testing_group(content)
Definition: cmake.py:260
def remove_command(self, cmd)
Definition: cmake.py:358
def add_command(self, cmd)
Definition: cmake.py:337
def get_executable_source(self)
Definition: cmake.py:395
def get_test_section(self, create_if_needed=False)
Definition: cmake.py:439
def get_section(self, key)
Definition: cmake.py:188
def resolve_variables(self, var)
Definition: cmake.py:295
def get_style(cmake)
Definition: cmake.py:25
def __init__(self, name='', values=None, style=None)
Definition: cmake.py:137
def section_check(self, items, cmd_name, section_name='', zero_okay=False, alpha_order=True)
Definition: cmake.py:471
def get_sections(self, key)
Definition: cmake.py:194
def get_library_source(self)
Definition: cmake.py:392
def add_values(self, new_values, alpha_order=True)
Definition: cmake.py:151
def add(self, section)
Definition: cmake.py:201
def get_sort_key(content, anchors, ordering)
Definition: cmake.py:90
def upgrade_minimum_version(self, new_version)
Definition: cmake.py:541
def get_source_build_rules(self, tag, resolve_target_name=False)
Definition: cmake.py:371
def get_command_section(self, command_name, section_name)
Definition: cmake.py:458
def get_ordering(style)
Definition: cmake.py:58
def get_insertion_index(self, cmd)
Definition: cmake.py:320


ros_introspection
Author(s):
autogenerated on Wed Jun 22 2022 02:45:33