00001 import collections
00002 import re
00003
00004 VARIABLE_PATTERN = re.compile(r'\$\{([^\}]+)\}')
00005 QUOTED_PATTERN = re.compile(r'"([^"]+)"')
00006
00007 BUILD_TARGET_COMMANDS = ['add_library', 'add_executable', 'add_rostest', 'add_dependencies', 'target_link_libraries']
00008
00009 ORDERING = ['cmake_minimum_required', 'project', 'set_directory_properties', 'find_package', 'pkg_check_modules',
00010 'set', 'catkin_generate_virtualenv', 'catkin_python_setup', 'add_definitions',
00011 'add_message_files', 'add_service_files', 'add_action_files',
00012 'generate_dynamic_reconfigure_options', 'generate_messages', 'catkin_package', 'catkin_metapackage',
00013 BUILD_TARGET_COMMANDS + ['include_directories'],
00014 ['catkin_download_test_data', 'roslint_cpp', 'roslint_python', 'roslint_add_test', 'catkin_add_nosetests'],
00015 'catkin_add_gtest', 'group',
00016 ['install', 'catkin_install_python']]
00017
00018
00019 def get_ordering_index(command_name):
00020 for i, o in enumerate(ORDERING):
00021 if type(o) == list:
00022 if command_name in o:
00023 return i
00024 elif command_name == o:
00025 return i
00026 if command_name:
00027 print('\tUnsure of ordering for', command_name)
00028 return len(ORDERING)
00029
00030
00031 def get_sort_key(content, anchors):
00032 if content is None:
00033 return len(ORDERING) + 1, None
00034 index = None
00035 key = None
00036 if content.__class__ == CommandGroup:
00037 index = get_ordering_index('group')
00038 sections = content.initial_tag.get_real_sections()
00039 if len(sections) > 0:
00040 key = sections[0].name
00041 else:
00042 index = get_ordering_index(content.command_name)
00043 if content.command_name in BUILD_TARGET_COMMANDS:
00044 token = content.first_token()
00045 if token not in anchors:
00046 anchors.append(token)
00047 key = anchors.index(token), BUILD_TARGET_COMMANDS.index(content.command_name)
00048 elif content.command_name == 'include_directories' and 'include_directories' in anchors:
00049 key = anchors.index('include_directories')
00050 return index, key
00051
00052
00053 class SectionStyle:
00054 def __init__(self, prename='', name_val_sep=' ', val_sep=' '):
00055 self.prename = prename
00056 self.name_val_sep = name_val_sep
00057 self.val_sep = val_sep
00058
00059 def __repr__(self):
00060 return 'SectionStyle(%s, %s, %s)' % (repr(self.prename), repr(self.name_val_sep), repr(self.val_sep))
00061
00062
00063 class Section:
00064 def __init__(self, name='', values=None, style=None):
00065 self.name = name
00066 if values is None:
00067 self.values = []
00068 else:
00069 self.values = list(values)
00070 if style:
00071 self.style = style
00072 else:
00073 self.style = SectionStyle()
00074
00075 def add(self, v):
00076 self.values.append(v)
00077
00078 def is_valid(self):
00079 return len(self.name) > 0 or len(self.values) > 0
00080
00081 def __repr__(self):
00082 s = self.style.prename
00083 if len(self.name) > 0:
00084 s += self.name
00085 if len(self.values) > 0:
00086 s += self.style.name_val_sep
00087 s += self.style.val_sep.join(self.values)
00088 return s
00089
00090
00091 class Command:
00092 def __init__(self, command_name):
00093 self.command_name = command_name
00094 self.original = None
00095 self.changed = False
00096 self.pre_paren = ''
00097 self.sections = []
00098
00099 def get_real_sections(self):
00100 return [s for s in self.sections if type(s) != str]
00101
00102 def get_section(self, key):
00103 for s in self.get_real_sections():
00104 if s.name == key:
00105 return s
00106 return None
00107
00108 def get_sections(self, key):
00109 return [s for s in self.get_real_sections() if s.name == key]
00110
00111 def add_section(self, key, values=None, style=None):
00112 self.sections.append(Section(key, values, style))
00113 self.changed = True
00114
00115 def add(self, section):
00116 if section:
00117 self.sections.append(section)
00118 self.changed = True
00119
00120 def first_token(self):
00121 return self.get_real_sections()[0].values[0]
00122
00123 def remove_sections(self, key):
00124 bad_sections = self.get_sections(key)
00125 if not bad_sections:
00126 return
00127 self.changed = True
00128 self.sections = [section for section in self.sections if section not in bad_sections]
00129 if len(self.sections) == 1 and type(self.sections[0]) == str:
00130 self.sections = []
00131
00132 def get_tokens(self, include_name=False):
00133 tokens = []
00134 for section in self.get_real_sections():
00135 if include_name and section.name:
00136 tokens.append(section.name)
00137 tokens += section.values
00138 return tokens
00139
00140 def add_token(self, s):
00141 sections = self.get_real_sections()
00142 if len(sections) == 0:
00143 self.add(Section(values=[s]))
00144 else:
00145 last = sections[-1]
00146 last.values.append(s)
00147 self.changed = True
00148
00149 def __repr__(self):
00150 if self.original and not self.changed:
00151 return self.original
00152
00153 s = self.command_name + self.pre_paren + '('
00154 for section in map(str, self.sections):
00155 if s[-1] not in '( \n' and section[0] not in ' \n':
00156 s += ' '
00157 s += section
00158 if '\n' in s and s[-1] != '\n':
00159 s += '\n'
00160 s += ')'
00161 return s
00162
00163
00164 class CommandGroup:
00165 def __init__(self, initial_tag, sub, close_tag):
00166 self.initial_tag = initial_tag
00167 self.sub = sub
00168 self.close_tag = close_tag
00169
00170 def __repr__(self):
00171 return str(self.initial_tag) + str(self.sub) + str(self.close_tag)
00172
00173
00174 class CMake:
00175 def __init__(self, file_path=None, initial_contents=None, depth=0):
00176 self.file_path = file_path
00177 if initial_contents is None:
00178 self.contents = []
00179 else:
00180 self.contents = initial_contents
00181 self.content_map = collections.defaultdict(list)
00182 for content in self.contents:
00183 if content.__class__ == Command:
00184 self.content_map[content.command_name].append(content)
00185 elif content.__class__ == CommandGroup:
00186 self.content_map['group'].append(content)
00187 self.depth = depth
00188
00189 self.variables = {}
00190 for cmd in self.content_map['set']:
00191 tokens = cmd.get_tokens(include_name=True)
00192 self.variables[tokens[0]] = ' '.join(tokens[1:])
00193 self.variables['PROJECT_NAME'] = self.get_project_name()
00194
00195 def get_project_name(self):
00196 project_tags = self.content_map['project']
00197 if not project_tags:
00198 return ''
00199
00200 return project_tags[0].get_tokens(include_name=True)[0]
00201
00202 def resolve_variables(self, var):
00203 if type(var) == str:
00204 s = var
00205 m = VARIABLE_PATTERN.search(s)
00206 if not m:
00207 return s
00208
00209 for k, v in self.variables.iteritems():
00210 s = s.replace('${%s}' % k, v)
00211 return s
00212 else:
00213 tokens = []
00214 for token in var:
00215 if token and token[0] == '#':
00216 continue
00217 m = QUOTED_PATTERN.match(token)
00218 if m:
00219 token = m.group(1)
00220 token = self.resolve_variables(token)
00221 tokens += token.split(' ')
00222 return tokens
00223
00224 def get_resolved_tokens(self, cmd, include_name=False):
00225 return self.resolve_variables(cmd.get_tokens(include_name))
00226
00227 def get_insertion_index(self, cmd):
00228 anchors = self.get_ordered_build_targets()
00229
00230 new_key = get_sort_key(cmd, anchors)
00231 i_index = 0
00232
00233 for i, content in enumerate(self.contents):
00234 if type(content) == str:
00235 continue
00236 key = get_sort_key(content, anchors)
00237 if key <= new_key:
00238 i_index = i + 1
00239 elif key[0] != len(ORDERING):
00240 return i_index
00241 return len(self.contents)
00242
00243 def add_command(self, cmd):
00244 i_index = self.get_insertion_index(cmd)
00245 sub_contents = []
00246 if i_index > 0 and type(self.contents[i_index - 1]) != str:
00247 sub_contents.append('\n')
00248 if self.depth > 0:
00249 sub_contents.append(' ' * self.depth)
00250 sub_contents.append(cmd)
00251 sub_contents.append('\n')
00252 else:
00253 sub_contents.append(cmd)
00254 if i_index == len(self.contents):
00255 sub_contents.append('\n')
00256
00257 self.contents = self.contents[:i_index] + sub_contents + self.contents[i_index:]
00258
00259 if cmd.__class__ == Command:
00260 self.content_map[cmd.command_name].append(cmd)
00261 elif cmd.__class__ == CommandGroup:
00262 self.content_map['group'].append(cmd)
00263
00264 def remove_command(self, cmd):
00265 print('\tRemoving %s' % str(cmd).replace('\n', ' ').replace(' ', ''))
00266 self.contents.remove(cmd)
00267 self.content_map[cmd.command_name].remove(cmd)
00268
00269 def remove_all_commands(self, cmd_name):
00270 cmds = list(self.content_map[cmd_name])
00271 for cmd in cmds:
00272 self.remove_command(cmd)
00273
00274 def get_source_build_rules(self, tag, resolve_target_name=False):
00275 rules = {}
00276 for cmd in self.content_map[tag]:
00277 resolved_tokens = self.get_resolved_tokens(cmd, True)
00278
00279 if resolve_target_name:
00280 target = resolved_tokens[0]
00281 else:
00282 tokens = cmd.get_tokens(True)
00283 target = tokens[0]
00284
00285 deps = resolved_tokens[1:]
00286 rules[target] = deps
00287 return rules
00288
00289 def get_source_helper(self, tag):
00290 lib_src = set()
00291 for target, deps in self.get_source_build_rules(tag).iteritems():
00292 lib_src.update(deps)
00293 return lib_src
00294
00295 def get_library_source(self):
00296 return self.get_source_helper('add_library')
00297
00298 def get_executable_source(self):
00299 return self.get_source_helper('add_executable')
00300
00301 def get_libraries(self):
00302 return self.get_source_build_rules('add_library').keys()
00303
00304 def get_executables(self):
00305 return self.get_source_build_rules('add_executable').keys()
00306
00307 def get_target_build_rules(self):
00308 targets = {}
00309 targets.update(self.get_source_build_rules('add_library'))
00310 targets.update(self.get_source_build_rules('add_executable'))
00311 return targets
00312
00313 def get_ordered_build_targets(self):
00314 targets = []
00315 for content in self.contents:
00316 if content.__class__ != Command:
00317 continue
00318 if content.command_name == 'include_directories':
00319 targets.append('include_directories')
00320 continue
00321 elif content.command_name not in BUILD_TARGET_COMMANDS:
00322 continue
00323 token = content.first_token()
00324 if token not in targets:
00325 targets.append(token)
00326 return targets
00327
00328 def get_test_sections(self):
00329 sections = []
00330 for content in self.content_map['group']:
00331 cmd = content.initial_tag
00332 if cmd.command_name != 'if' or len(cmd.sections) == 0 or cmd.sections[0].name != 'CATKIN_ENABLE_TESTING':
00333 continue
00334 sections.append(content.sub)
00335 return sections
00336
00337 def get_test_source(self):
00338 test_files = set()
00339 for sub in self.get_test_sections():
00340 test_files.update(sub.get_library_source())
00341 test_files.update(sub.get_executable_source())
00342 return test_files
00343
00344 def get_test_section(self, create_if_needed=False):
00345 sections = self.get_test_sections()
00346 if len(sections) > 0:
00347 return sections[0]
00348 if not create_if_needed:
00349 return None
00350
00351
00352 initial_cmd = Command('if')
00353 initial_cmd.add_section('CATKIN_ENABLE_TESTING')
00354
00355 test_contents = CMake(initial_contents=['\n'], depth=self.depth + 1)
00356
00357 final_cmd = Command('endif')
00358
00359 cg = CommandGroup(initial_cmd, test_contents, final_cmd)
00360 self.add_command(cg)
00361 return cg.sub
00362
00363 def get_command_section(self, command_name, section_name):
00364 """ Return the first command that matches the command name and
00365 has a matching section name. If the section name is not found,
00366 return a command with the matching command name"""
00367 if len(self.content_map[command_name]) == 0:
00368 return None, None
00369 for cmd in self.content_map[command_name]:
00370 s = cmd.get_section(section_name)
00371 if s:
00372 return cmd, s
00373 return self.content_map[command_name][0], None
00374
00375 def section_check(self, items, cmd_name, section_name='', zero_okay=False):
00376 """ This function ensures that there's a CMake command of the given type
00377 with the given section name and items somewhere in the file. """
00378 if len(items) == 0 and not zero_okay:
00379 return
00380
00381 cmd, section = self.get_command_section(cmd_name, section_name)
00382
00383 if cmd is None:
00384 cmd = Command(cmd_name)
00385 self.add_command(cmd)
00386
00387 if section is None:
00388 cmd.add_section(section_name, sorted(items))
00389 else:
00390 existing = self.resolve_variables(section.values)
00391 needed_items = [item for item in items if item not in existing]
00392 section.values += sorted(needed_items)
00393 cmd.changed = True
00394
00395 def __repr__(self):
00396 return ''.join(map(str, self.contents))
00397
00398 def write(self, fn=None):
00399 if fn is None:
00400 fn = self.file_path
00401 with open(fn, 'w') as cmake:
00402 cmake.write(str(self))