Skip to content

Instantly share code, notes, and snippets.

@jonbinney
Last active December 17, 2015 05:29
Show Gist options
  • Save jonbinney/5558335 to your computer and use it in GitHub Desktop.
Save jonbinney/5558335 to your computer and use it in GitHub Desktop.
Recursively checks a catkin workspace for missing _gencpp dependencies
#!/usr/bin/env python
import sys, re, os, os.path
def find_files(root_dir, exclude_re, include_re):
files = []
for dirpath, dirnames, filenames in os.walk(root_dir, topdown=True):
for filename in filenames:
if include_re.match(filename):
files.append((dirpath, filename))
for dirname in dirnames:
# don't recurse into excluded directories
if exclude_re.match(dirname):
dirnames.remove(dirname)
return files
# look for all add_executable macros in a CMakeLists.txt
add_target_re = re.compile('(add_executable|add_library)\(\s*(\S+)([^\(]*)\)', re.IGNORECASE | re.MULTILINE)
# look for all add_dependencies macros in a CMakeLists.txt
add_dependencies_re = re.compile('add_dependencies\(\s*(\S+)([^\(]*)\)', re.IGNORECASE | re.MULTILINE)
# check whether a filename could be a c++ source file
cpp_source_file_re = re.compile('.+(\.cpp|\.h|\.hh|\.cc|\.c|\.hpp)$')
# look for includes that look like they are an autogenerated message
# header, and find the name of the package the message is in
# for now, relies on the fact that most message packages end in _msg
# is there a more reliable heuristic for this?
msg_include_re = re.compile('\#include\s*\<(\S+_msgs)\S+\>')
def get_cpp_message_packages_used(source_file_path):
'''
Looks through a C++ source or header, and tries to
guess what packages it uses messages from.
'''
f = open(source_file_path)
source_str = f.read()
f.close()
message_packages = set(msg_include_re.findall(source_str))
return message_packages
def check_cmakelists(dirpath):
'''
For each target in the given CMakeLists.txt file,
Args:
dirpath (str) - absolute path to the directory the cmakelists is in
'''
f = open(os.path.join(dirpath, 'CMakeLists.txt'))
cmakelists_str = f.read()
f.close()
print os.path.join(dirpath, filename)
# find all dependencies explicitly declared for each target
target_message_deps = {}
matches = add_dependencies_re.findall(cmakelists_str)
for target_name, args_str in matches:
if target_name not in target_message_deps:
target_message_deps[target_name] = set()
for dep in args_str.split():
if dep[-7:] == '_gencpp':
target_message_deps[target_name].add(dep[:-7])
matches = add_target_re.findall(cmakelists_str)
for macro_name, target_name, args_str in matches:
args = args_str.split()
declared_message_deps = target_message_deps.get(target_name, set())
if verbose:
print ' Target: %s' % target_name
if verbose:
print ' declared:', declared_message_deps
for arg in args:
if cpp_source_file_re.match(arg):
file_path = os.path.join(dirpath, arg)
# check what message packages this source file needs
try:
message_packages_used = get_cpp_message_packages_used(file_path)
except:
print ' Unable to process file %s' % arg
continue
if verbose:
print ' used in %s:' % (arg,), message_packages_used
for pkg in message_packages_used:
if pkg not in declared_message_deps:
print ' ' + '%s used for target %s but no %s_gencpp dependency declared' % (
pkg, target_name, pkg)
if len(sys.argv) > 1:
root_dir = sys.argv[1]
else:
root_dir = os.getcwd()
verbose = False
print 'Recursively checking %s' % root_dir
# don't recurse into directories matching this
exclude_re = re.compile(r'.*\.git')
include_re = re.compile(r'CMakeLists.txt')
cmakelists_files = find_files(root_dir, exclude_re, include_re)
for dirpath, filename in cmakelists_files:
check_cmakelists(dirpath)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment