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


ros_introspection
Author(s):
autogenerated on Wed Mar 3 2021 03:56:00