00001 import os
00002 import fnmatch
00003 import collections
00004 from ros_introspection.cmake import Command
00005 from util import roscompile
00006
00007 FILES_TO_NOT_INSTALL = ['CHANGELOG.rst', 'README.md', '.travis.yml', 'bitbucket-pipelines.yml']
00008
00009
00010
00011
00012 INSTALL_CONFIGS = {
00013 'exec': ('TARGETS', {'${CATKIN_PACKAGE_BIN_DESTINATION}': ['RUNTIME DESTINATION']}),
00014 'library': ('TARGETS', {'${CATKIN_PACKAGE_LIB_DESTINATION}': ['ARCHIVE DESTINATION', 'LIBRARY DESTINATION'],
00015 '${CATKIN_GLOBAL_BIN_DESTINATION}': ['RUNTIME DESTINATION']}),
00016 'headers': ('FILES', {'${CATKIN_PACKAGE_INCLUDE_DESTINATION}': ['DESTINATION']}),
00017 'misc': ('FILES', {'${CATKIN_PACKAGE_SHARE_DESTINATION}': ['DESTINATION']})
00018 }
00019
00020
00021 def get_install_type(destination):
00022 """ For a given catkin destination, return the matching install type """
00023 for name, (kw, destination_map) in INSTALL_CONFIGS.iteritems():
00024 if destination in destination_map:
00025 return name
00026
00027
00028 def get_install_types(cmd, subfolder=''):
00029 """ For a given CMake command, determine the install type(s) that this command uses.
00030
00031 If there is a non-empty subfolder, we only return the install types if the command
00032 installs into the catkin_destination with the given subfolder """
00033 types = set()
00034 for section in cmd.get_sections('DESTINATION'):
00035 the_folder = section.values[0]
00036 if len(subfolder) > 0:
00037 if subfolder not in the_folder:
00038 continue
00039 the_folder = the_folder.replace('/' + subfolder, '')
00040 type_ = get_install_type(the_folder)
00041 if type_:
00042 types.add(type_)
00043 return types
00044
00045
00046 def get_multiword_section(cmd, words):
00047 """ Our definition of a CMake command section is ONE all-caps word followed by tokens.
00048 Installing stuff requires these weird TWO word sections (i.e. ARCHIVE DESTINATION).
00049
00050 Ergo, we need to find the section that matches the second word, presuming the section
00051 before matched the first word.
00052 """
00053 i = 0
00054 for section in cmd.get_real_sections():
00055 if section.name == words[i]:
00056
00057 if i < len(words) - 1:
00058
00059 i += 1
00060 else:
00061
00062 return section
00063 else:
00064
00065 i = 0
00066
00067
00068 def matches_patterns(item, patterns):
00069 for pattern in patterns:
00070 if pattern[0] == pattern[-1] and pattern[0] == '"':
00071 pattern = pattern[1:-1]
00072 if fnmatch.fnmatch(item, pattern):
00073 return True
00074
00075
00076 def check_complex_section(cmd, key, value):
00077 """ This finds the appopriate section of the command (with a possibly multiword key, see get_multiword_section)
00078 and ensures the given value is in it. If the appopriate section is not found, it adds it. """
00079
00080 words = key.split()
00081 if len(words) == 1:
00082 section = cmd.get_section(key)
00083 else:
00084 section = get_multiword_section(cmd, words)
00085
00086 if section:
00087 if value not in section.values:
00088 section.add(value)
00089 cmd.changed = True
00090 else:
00091 cmd.add_section(key, [value])
00092
00093
00094 def install_sections(cmd, destination_map, subfolder=''):
00095 """ For a given command and destination_map, ensure that the command has all
00096 the appropriate CMake sections with the matching catkin destinations.
00097 If the subfolder is defined, the subfolder is appended to the catkin destination."""
00098 for destination, section_names in destination_map.iteritems():
00099 for section_name in section_names:
00100 if len(subfolder) > 0:
00101 destination = os.path.join(destination, subfolder)
00102 check_complex_section(cmd, section_name, destination)
00103
00104
00105 def remove_install_section(cmd, destination_map):
00106 empty_sections_to_remove = {}
00107 for destination, section_names in destination_map.iteritems():
00108 for section_name in section_names:
00109 parts = section_name.split()
00110 if len(parts) == 2:
00111 empty_sections_to_remove[parts[0]] = destination
00112 sections = cmd.get_real_sections()
00113 to_remove = []
00114 for i, section in enumerate(sections):
00115 if section.name not in empty_sections_to_remove or len(section.values) != 0:
00116 continue
00117 next = sections[i + 1]
00118 dest = empty_sections_to_remove[section.name]
00119 if next.name == 'DESTINATION' and len(next.values) == 1 and next.values[0] == dest:
00120 to_remove.append(section)
00121 to_remove.append(next)
00122 if len(to_remove) > 0:
00123 for section in to_remove:
00124 cmd.sections.remove(section)
00125 cmd.changed = True
00126
00127
00128 def get_commands_by_type(cmake, name, subfolder=''):
00129 matches = []
00130 for cmd in cmake.content_map['install']:
00131 if name in get_install_types(cmd, subfolder):
00132 matches.append(cmd)
00133 return matches
00134
00135
00136 def install_section_check(cmake, items, install_type, directory=False, subfolder=''):
00137 section_name, destination_map = INSTALL_CONFIGS[install_type]
00138 if directory and section_name == 'FILES':
00139 section_name = 'DIRECTORY'
00140 cmds = get_commands_by_type(cmake, install_type, subfolder)
00141 if len(items) == 0:
00142 for cmd in cmds:
00143 if len(get_install_types(cmd)) == 1:
00144 cmake.remove_command(cmd)
00145 else:
00146 remove_install_section(cmd, destination_map)
00147 return
00148
00149 cmd = None
00150 items = [os.path.join(subfolder, item) for item in items]
00151 for cmd in cmds:
00152 install_sections(cmd, destination_map, subfolder)
00153 section = cmd.get_section(section_name)
00154 if not section:
00155 if section_name != 'FILES':
00156 continue
00157 section = cmd.get_section('DIRECTORY')
00158 if not section:
00159 continue
00160 pattern = get_multiword_section(cmd, ['FILES_MATCHING', 'PATTERN'])
00161 nonmatching_items = []
00162 for item in items:
00163 if pattern and not matches_patterns(item, pattern.values):
00164 nonmatching_items.append(item)
00165 items = nonmatching_items
00166 else:
00167
00168 section.values = [value for value in section.values if value in items]
00169 items = [item for item in items if item not in section.values]
00170
00171 if len(items) == 0:
00172 return
00173
00174 print('\tInstalling', ', '.join(items))
00175 if cmd is None:
00176 cmd = Command('install')
00177 cmd.add_section(section_name, items)
00178 cmake.add_command(cmd)
00179 install_sections(cmd, destination_map, subfolder)
00180 elif section:
00181 section = cmd.get_section(section_name)
00182 section.values += items
00183 cmd.changed = True
00184
00185
00186 @roscompile
00187 def update_cplusplus_installs(package):
00188 install_section_check(package.cmake, package.cmake.get_executables(), 'exec')
00189 install_section_check(package.cmake, package.cmake.get_libraries(), 'library')
00190 if package.name and package.source_code.has_header_files():
00191 install_section_check(package.cmake, ['include/${PROJECT_NAME}/'], 'headers', directory=True)
00192
00193
00194 @roscompile
00195 def update_misc_installs(package):
00196 extra_files_by_folder = collections.defaultdict(list)
00197 rel_paths = [obj.rel_fn for obj in package.launches + package.plugin_configs] + package.misc_files
00198 for rel_path in sorted(rel_paths):
00199 if rel_path in FILES_TO_NOT_INSTALL:
00200 continue
00201 path, base = os.path.split(rel_path)
00202 extra_files_by_folder[path].append(base)
00203
00204 for folder, files in extra_files_by_folder.iteritems():
00205 install_section_check(package.cmake, files, 'misc', subfolder=folder)
00206
00207
00208 @roscompile
00209 def fix_double_directory_installs(package):
00210 for cmd in package.cmake.content_map['install']:
00211 dir_section = cmd.get_section('DIRECTORY')
00212 dest_sections = cmd.get_sections('DESTINATION')
00213
00214 if not dir_section or not dest_sections:
00215 continue
00216 directory = dir_section.values[0]
00217 final_slash = directory[-1] == '/'
00218
00219 for section in dest_sections:
00220 destination = section.values[0]
00221 if not final_slash and destination.endswith(directory):
00222
00223 section.values[0] = destination[:-len(directory) - 1]
00224 cmd.changed = True