installs.py
Go to the documentation of this file.
1 import collections
2 import fnmatch
3 import os
4 
5 from ros_introspection.cmake import Command, SectionStyle
6 
7 from .cmake import NEWLINE_PLUS_8
8 from .util import roscompile
9 
10 FILES_TO_NOT_INSTALL = ['CHANGELOG.rst', 'README.md', '.travis.yml', 'bitbucket-pipelines.yml']
11 
12 # We define four install types, using a unique string to identify each (the keys in this dict)
13 # The values define a tuple, where the first element is a CMake keyword.
14 # The second value is a dictionary mapping catkin destinations to some CMake section names.
15 INSTALL_CONFIGS = {
16  'exec': ('TARGETS', {'${CATKIN_PACKAGE_BIN_DESTINATION}': ['RUNTIME DESTINATION']}),
17  'library': ('TARGETS', {'${CATKIN_PACKAGE_LIB_DESTINATION}': ['ARCHIVE DESTINATION', 'LIBRARY DESTINATION'],
18  '${CATKIN_GLOBAL_BIN_DESTINATION}': ['RUNTIME DESTINATION']}),
19  'headers': ('FILES', {'${CATKIN_PACKAGE_INCLUDE_DESTINATION}': ['DESTINATION']}),
20  'misc': ('FILES', {'${CATKIN_PACKAGE_SHARE_DESTINATION}': ['DESTINATION']})
21 }
22 
23 
24 def get_install_type(destination):
25  """For a given catkin destination, return the matching install type."""
26  for name, (kw, destination_map) in INSTALL_CONFIGS.items():
27  if destination in destination_map:
28  return name
29 
30 
31 def get_install_types(cmd, subfolder=''):
32  """For a given CMake command, determine the install type(s) that this command uses.
33 
34  If there is a non-empty subfolder, we only return the install types if the command
35  installs into the catkin_destination with the given subfolder
36  """
37  types = set()
38  for section in cmd.get_sections('DESTINATION'):
39  the_folder = section.values[0]
40  if len(subfolder) > 0:
41  if subfolder not in the_folder:
42  continue
43  the_folder = the_folder.replace('/' + subfolder, '')
44  type_ = get_install_type(the_folder)
45  if type_:
46  types.add(type_)
47  return types
48 
49 
50 def get_multiword_section(cmd, words):
51  """Find a section that matches the last word, assuming all the previous sections matched the other words.
52 
53  Our definition of a CMake command section is ONE all-caps word followed by tokens.
54  Installing stuff requires these weird TWO word sections (i.e. ARCHIVE DESTINATION).
55 
56  Ergo, we need to find the section that matches the second word, presuming the section
57  before matched the first word.
58  """
59  i = 0
60  for section in cmd.get_real_sections():
61  if section.name == words[i]:
62  # One word matches
63  if i < len(words) - 1:
64  # If there are more words, increment the counter
65  i += 1
66  else:
67  # Otherwise, we matched all the words. Return this section
68  return section
69  else:
70  # If the word doesn't match, we need to start searching from the first word again
71  i = 0
72 
73 
74 def matches_patterns(item, patterns):
75  for pattern in patterns:
76  if pattern[0] == pattern[-1] and pattern[0] == '"':
77  pattern = pattern[1:-1]
78  if fnmatch.fnmatch(item, pattern):
79  return True
80 
81 
82 def check_complex_section(cmd, key, value):
83  """Find the section matching the key and ensure the value is in it.
84 
85  Key could be multiple words, see get_multiword_section.
86  If the appopriate section is not found, it adds it.
87  """
88  words = key.split()
89  if len(words) == 1:
90  section = cmd.get_section(key)
91  else:
92  section = get_multiword_section(cmd, words)
93 
94  if section:
95  if value not in section.values:
96  section.add(value)
97  cmd.changed = True
98  else:
99  cmd.add_section(key, [value], SectionStyle(NEWLINE_PLUS_8))
100 
101 
102 def install_sections(cmd, destination_map, subfolder=''):
103  """Ensure that the command has all the appropriate CMake sections with the matching catkin destinations.
104 
105  If the subfolder is defined, the subfolder is appended to the catkin destination.
106  """
107  for destination, section_names in destination_map.items():
108  for section_name in section_names:
109  if len(subfolder) > 0:
110  destination = os.path.join(destination, subfolder)
111  check_complex_section(cmd, section_name, destination)
112 
113 
114 def remove_install_section(cmd, destination_map):
115  empty_sections_to_remove = {}
116  for destination, section_names in destination_map.items():
117  for section_name in section_names:
118  parts = section_name.split()
119  if len(parts) == 2:
120  empty_sections_to_remove[parts[0]] = destination
121  sections = cmd.get_real_sections()
122  to_remove = []
123  for i, section in enumerate(sections):
124  if section.name not in empty_sections_to_remove or len(section.values) != 0:
125  continue
126  next_section = sections[i + 1]
127  dest = empty_sections_to_remove[section.name]
128  if next_section.name == 'DESTINATION' and len(next_section.values) == 1 and next_section.values[0] == dest:
129  to_remove.append(section)
130  to_remove.append(next_section)
131  if len(to_remove) > 0:
132  for section in to_remove:
133  cmd.sections.remove(section)
134  cmd.changed = True
135 
136 
137 def get_commands_by_type(cmake, name, subfolder=''):
138  matches = []
139  for cmd in cmake.content_map['install']:
140  if name in get_install_types(cmd, subfolder):
141  matches.append(cmd)
142  return matches
143 
144 
145 def install_section_check(cmake, items, install_type, directory=False, subfolder=''):
146  section_name, destination_map = INSTALL_CONFIGS[install_type]
147  if directory and section_name == 'FILES':
148  section_name = 'DIRECTORY'
149  cmds = get_commands_by_type(cmake, install_type, subfolder)
150  if len(items) == 0:
151  for cmd in cmds:
152  if len(get_install_types(cmd)) == 1:
153  cmake.remove_command(cmd)
154  else:
155  remove_install_section(cmd, destination_map)
156  return
157 
158  cmd = None
159  items = [os.path.join(subfolder, item) for item in items]
160  for cmd in cmds:
161  install_sections(cmd, destination_map, subfolder)
162  section = cmd.get_section(section_name)
163  if not section:
164  if section_name != 'FILES':
165  continue
166  section = cmd.get_section('DIRECTORY')
167  if not section:
168  continue
169  pattern = get_multiword_section(cmd, ['FILES_MATCHING', 'PATTERN'])
170  nonmatching_items = []
171  for item in items:
172  if pattern and not matches_patterns(item, pattern.values):
173  nonmatching_items.append(item)
174  items = nonmatching_items
175  else:
176  # We match the section
177  section.values = [value for value in section.values if value in items]
178  items = [item for item in items if item not in section.values]
179 
180  if len(items) == 0:
181  return
182 
183  print('\tInstalling %s' % ', '.join(items))
184  if cmd is None:
185  cmd = Command('install')
186  cmd.add_section(section_name, items)
187  cmake.add_command(cmd)
188  install_sections(cmd, destination_map, subfolder)
189  elif section:
190  # section = cmd.get_section(section_name)
191  section.values += items
192  cmd.changed = True
193 
194 
195 @roscompile
197  install_section_check(package.cmake, package.cmake.get_executables(), 'exec')
198  install_section_check(package.cmake, package.cmake.get_libraries(), 'library')
199  if package.name and package.source_code.has_header_files():
200  install_section_check(package.cmake, ['include/${PROJECT_NAME}/'], 'headers', directory=True)
201 
202 
203 @roscompile
204 def update_misc_installs(package):
205  extra_files_by_folder = collections.defaultdict(list)
206  rel_paths = [obj.rel_fn for obj in package.launches + package.plugin_configs + package.urdf_files]
207  rel_paths += package.misc_files
208  for rel_path in sorted(rel_paths):
209  if rel_path in FILES_TO_NOT_INSTALL:
210  continue
211  path, base = os.path.split(rel_path)
212  extra_files_by_folder[path].append(base)
213 
214  for folder, files in sorted(extra_files_by_folder.items()):
215  install_section_check(package.cmake, files, 'misc', subfolder=folder)
216 
217 
218 @roscompile
220  for cmd in package.cmake.content_map['install']:
221  dir_section = cmd.get_section('DIRECTORY')
222  dest_sections = cmd.get_sections('DESTINATION')
223 
224  if not dir_section or not dest_sections:
225  continue
226  directory = dir_section.values[0]
227  final_slash = directory[-1] == '/'
228 
229  for section in dest_sections:
230  destination = section.values[0]
231  if not final_slash and destination.endswith(directory):
232  # Remove double directory and final slash
233  section.values[0] = destination[:-len(directory) - 1]
234  cmd.changed = True
def matches_patterns(item, patterns)
Definition: installs.py:74
def check_complex_section(cmd, key, value)
Definition: installs.py:82
def get_multiword_section(cmd, words)
Definition: installs.py:50
def update_cplusplus_installs(package)
Definition: installs.py:196
def fix_double_directory_installs(package)
Definition: installs.py:219
def install_section_check(cmake, items, install_type, directory=False, subfolder='')
Definition: installs.py:145
def update_misc_installs(package)
Definition: installs.py:204
def remove_install_section(cmd, destination_map)
Definition: installs.py:114
def get_commands_by_type(cmake, name, subfolder='')
Definition: installs.py:137
def get_install_types(cmd, subfolder='')
Definition: installs.py:31
def install_sections(cmd, destination_map, subfolder='')
Definition: installs.py:102
def get_install_type(destination)
Definition: installs.py:24


roscompile
Author(s):
autogenerated on Wed Mar 3 2021 03:56:01