_setup_util.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #!/usr/bin/python2
  2. # -*- coding: utf-8 -*-
  3. # Software License Agreement (BSD License)
  4. #
  5. # Copyright (c) 2012, Willow Garage, Inc.
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions
  10. # are met:
  11. #
  12. # * Redistributions of source code must retain the above copyright
  13. # notice, this list of conditions and the following disclaimer.
  14. # * Redistributions in binary form must reproduce the above
  15. # copyright notice, this list of conditions and the following
  16. # disclaimer in the documentation and/or other materials provided
  17. # with the distribution.
  18. # * Neither the name of Willow Garage, Inc. nor the names of its
  19. # contributors may be used to endorse or promote products derived
  20. # from this software without specific prior written permission.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  25. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  26. # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  27. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  28. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  29. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  30. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  31. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  32. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. # POSSIBILITY OF SUCH DAMAGE.
  34. """This file generates shell code for the setup.SHELL scripts to set environment variables."""
  35. from __future__ import print_function
  36. import argparse
  37. import copy
  38. import errno
  39. import os
  40. import platform
  41. import sys
  42. CATKIN_MARKER_FILE = '.catkin'
  43. system = platform.system()
  44. IS_DARWIN = (system == 'Darwin')
  45. IS_WINDOWS = (system == 'Windows')
  46. PATH_TO_ADD_SUFFIX = ['bin']
  47. if IS_WINDOWS:
  48. # while catkin recommends putting dll's into bin, 3rd party packages often put dll's into lib
  49. # since Windows finds dll's via the PATH variable, prepend it with path to lib
  50. PATH_TO_ADD_SUFFIX.extend([['lib', os.path.join('lib', 'x86_64-linux-gnu')]])
  51. # subfolder of workspace prepended to CMAKE_PREFIX_PATH
  52. ENV_VAR_SUBFOLDERS = {
  53. 'CMAKE_PREFIX_PATH': '',
  54. 'LD_LIBRARY_PATH' if not IS_DARWIN else 'DYLD_LIBRARY_PATH': ['lib', os.path.join('lib', 'x86_64-linux-gnu')],
  55. 'PATH': PATH_TO_ADD_SUFFIX,
  56. 'PKG_CONFIG_PATH': [os.path.join('lib', 'pkgconfig'), os.path.join('lib', 'x86_64-linux-gnu', 'pkgconfig')],
  57. 'PYTHONPATH': 'lib/python2.7/dist-packages',
  58. }
  59. def rollback_env_variables(environ, env_var_subfolders):
  60. """
  61. Generate shell code to reset environment variables.
  62. by unrolling modifications based on all workspaces in CMAKE_PREFIX_PATH.
  63. This does not cover modifications performed by environment hooks.
  64. """
  65. lines = []
  66. unmodified_environ = copy.copy(environ)
  67. for key in sorted(env_var_subfolders.keys()):
  68. subfolders = env_var_subfolders[key]
  69. if not isinstance(subfolders, list):
  70. subfolders = [subfolders]
  71. value = _rollback_env_variable(unmodified_environ, key, subfolders)
  72. if value is not None:
  73. environ[key] = value
  74. lines.append(assignment(key, value))
  75. if lines:
  76. lines.insert(0, comment('reset environment variables by unrolling modifications based on all workspaces in CMAKE_PREFIX_PATH'))
  77. return lines
  78. def _rollback_env_variable(environ, name, subfolders):
  79. """
  80. For each catkin workspace in CMAKE_PREFIX_PATH remove the first entry from env[NAME] matching workspace + subfolder.
  81. :param subfolders: list of str '' or subfoldername that may start with '/'
  82. :returns: the updated value of the environment variable.
  83. """
  84. value = environ[name] if name in environ else ''
  85. env_paths = [path for path in value.split(os.pathsep) if path]
  86. value_modified = False
  87. for subfolder in subfolders:
  88. if subfolder:
  89. if subfolder.startswith(os.path.sep) or (os.path.altsep and subfolder.startswith(os.path.altsep)):
  90. subfolder = subfolder[1:]
  91. if subfolder.endswith(os.path.sep) or (os.path.altsep and subfolder.endswith(os.path.altsep)):
  92. subfolder = subfolder[:-1]
  93. for ws_path in _get_workspaces(environ, include_fuerte=True, include_non_existing=True):
  94. path_to_find = os.path.join(ws_path, subfolder) if subfolder else ws_path
  95. path_to_remove = None
  96. for env_path in env_paths:
  97. env_path_clean = env_path[:-1] if env_path and env_path[-1] in [os.path.sep, os.path.altsep] else env_path
  98. if env_path_clean == path_to_find:
  99. path_to_remove = env_path
  100. break
  101. if path_to_remove:
  102. env_paths.remove(path_to_remove)
  103. value_modified = True
  104. new_value = os.pathsep.join(env_paths)
  105. return new_value if value_modified else None
  106. def _get_workspaces(environ, include_fuerte=False, include_non_existing=False):
  107. """
  108. Based on CMAKE_PREFIX_PATH return all catkin workspaces.
  109. :param include_fuerte: The flag if paths starting with '/opt/ros/fuerte' should be considered workspaces, ``bool``
  110. """
  111. # get all cmake prefix paths
  112. env_name = 'CMAKE_PREFIX_PATH'
  113. value = environ[env_name] if env_name in environ else ''
  114. paths = [path for path in value.split(os.pathsep) if path]
  115. # remove non-workspace paths
  116. workspaces = [path for path in paths if os.path.isfile(os.path.join(path, CATKIN_MARKER_FILE)) or (include_fuerte and path.startswith('/opt/ros/fuerte')) or (include_non_existing and not os.path.exists(path))]
  117. return workspaces
  118. def prepend_env_variables(environ, env_var_subfolders, workspaces):
  119. """Generate shell code to prepend environment variables for the all workspaces."""
  120. lines = []
  121. lines.append(comment('prepend folders of workspaces to environment variables'))
  122. paths = [path for path in workspaces.split(os.pathsep) if path]
  123. prefix = _prefix_env_variable(environ, 'CMAKE_PREFIX_PATH', paths, '')
  124. lines.append(prepend(environ, 'CMAKE_PREFIX_PATH', prefix))
  125. for key in sorted(key for key in env_var_subfolders.keys() if key != 'CMAKE_PREFIX_PATH'):
  126. subfolder = env_var_subfolders[key]
  127. prefix = _prefix_env_variable(environ, key, paths, subfolder)
  128. lines.append(prepend(environ, key, prefix))
  129. return lines
  130. def _prefix_env_variable(environ, name, paths, subfolders):
  131. """
  132. Return the prefix to prepend to the environment variable NAME.
  133. Adding any path in NEW_PATHS_STR without creating duplicate or empty items.
  134. """
  135. value = environ[name] if name in environ else ''
  136. environ_paths = [path for path in value.split(os.pathsep) if path]
  137. checked_paths = []
  138. for path in paths:
  139. if not isinstance(subfolders, list):
  140. subfolders = [subfolders]
  141. for subfolder in subfolders:
  142. path_tmp = path
  143. if subfolder:
  144. path_tmp = os.path.join(path_tmp, subfolder)
  145. # skip nonexistent paths
  146. if not os.path.exists(path_tmp):
  147. continue
  148. # exclude any path already in env and any path we already added
  149. if path_tmp not in environ_paths and path_tmp not in checked_paths:
  150. checked_paths.append(path_tmp)
  151. prefix_str = os.pathsep.join(checked_paths)
  152. if prefix_str != '' and environ_paths:
  153. prefix_str += os.pathsep
  154. return prefix_str
  155. def assignment(key, value):
  156. if not IS_WINDOWS:
  157. return 'export %s="%s"' % (key, value)
  158. else:
  159. return 'set %s=%s' % (key, value)
  160. def comment(msg):
  161. if not IS_WINDOWS:
  162. return '# %s' % msg
  163. else:
  164. return 'REM %s' % msg
  165. def prepend(environ, key, prefix):
  166. if key not in environ or not environ[key]:
  167. return assignment(key, prefix)
  168. if not IS_WINDOWS:
  169. return 'export %s="%s$%s"' % (key, prefix, key)
  170. else:
  171. return 'set %s=%s%%%s%%' % (key, prefix, key)
  172. def find_env_hooks(environ, cmake_prefix_path):
  173. """Generate shell code with found environment hooks for the all workspaces."""
  174. lines = []
  175. lines.append(comment('found environment hooks in workspaces'))
  176. generic_env_hooks = []
  177. generic_env_hooks_workspace = []
  178. specific_env_hooks = []
  179. specific_env_hooks_workspace = []
  180. generic_env_hooks_by_filename = {}
  181. specific_env_hooks_by_filename = {}
  182. generic_env_hook_ext = 'bat' if IS_WINDOWS else 'sh'
  183. specific_env_hook_ext = environ['CATKIN_SHELL'] if not IS_WINDOWS and 'CATKIN_SHELL' in environ and environ['CATKIN_SHELL'] else None
  184. # remove non-workspace paths
  185. workspaces = [path for path in cmake_prefix_path.split(os.pathsep) if path and os.path.isfile(os.path.join(path, CATKIN_MARKER_FILE))]
  186. for workspace in reversed(workspaces):
  187. env_hook_dir = os.path.join(workspace, 'etc', 'catkin', 'profile.d')
  188. if os.path.isdir(env_hook_dir):
  189. for filename in sorted(os.listdir(env_hook_dir)):
  190. if filename.endswith('.%s' % generic_env_hook_ext):
  191. # remove previous env hook with same name if present
  192. if filename in generic_env_hooks_by_filename:
  193. i = generic_env_hooks.index(generic_env_hooks_by_filename[filename])
  194. generic_env_hooks.pop(i)
  195. generic_env_hooks_workspace.pop(i)
  196. # append env hook
  197. generic_env_hooks.append(os.path.join(env_hook_dir, filename))
  198. generic_env_hooks_workspace.append(workspace)
  199. generic_env_hooks_by_filename[filename] = generic_env_hooks[-1]
  200. elif specific_env_hook_ext is not None and filename.endswith('.%s' % specific_env_hook_ext):
  201. # remove previous env hook with same name if present
  202. if filename in specific_env_hooks_by_filename:
  203. i = specific_env_hooks.index(specific_env_hooks_by_filename[filename])
  204. specific_env_hooks.pop(i)
  205. specific_env_hooks_workspace.pop(i)
  206. # append env hook
  207. specific_env_hooks.append(os.path.join(env_hook_dir, filename))
  208. specific_env_hooks_workspace.append(workspace)
  209. specific_env_hooks_by_filename[filename] = specific_env_hooks[-1]
  210. env_hooks = generic_env_hooks + specific_env_hooks
  211. env_hooks_workspace = generic_env_hooks_workspace + specific_env_hooks_workspace
  212. count = len(env_hooks)
  213. lines.append(assignment('_CATKIN_ENVIRONMENT_HOOKS_COUNT', count))
  214. for i in range(count):
  215. lines.append(assignment('_CATKIN_ENVIRONMENT_HOOKS_%d' % i, env_hooks[i]))
  216. lines.append(assignment('_CATKIN_ENVIRONMENT_HOOKS_%d_WORKSPACE' % i, env_hooks_workspace[i]))
  217. return lines
  218. def _parse_arguments(args=None):
  219. parser = argparse.ArgumentParser(description='Generates code blocks for the setup.SHELL script.')
  220. parser.add_argument('--extend', action='store_true', help='Skip unsetting previous environment variables to extend context')
  221. parser.add_argument('--local', action='store_true', help='Only consider this prefix path and ignore other prefix path in the environment')
  222. return parser.parse_known_args(args=args)[0]
  223. if __name__ == '__main__':
  224. try:
  225. try:
  226. args = _parse_arguments()
  227. except Exception as e:
  228. print(e, file=sys.stderr)
  229. sys.exit(1)
  230. if not args.local:
  231. # environment at generation time
  232. CMAKE_PREFIX_PATH = '/home/ipc2001/catkin_ws/devel;/opt/ros/melodic'.split(';')
  233. else:
  234. # don't consider any other prefix path than this one
  235. CMAKE_PREFIX_PATH = []
  236. # prepend current workspace if not already part of CPP
  237. base_path = os.path.dirname(__file__)
  238. # CMAKE_PREFIX_PATH uses forward slash on all platforms, but __file__ is platform dependent
  239. # base_path on Windows contains backward slashes, need to be converted to forward slashes before comparison
  240. if os.path.sep != '/':
  241. base_path = base_path.replace(os.path.sep, '/')
  242. if base_path not in CMAKE_PREFIX_PATH:
  243. CMAKE_PREFIX_PATH.insert(0, base_path)
  244. CMAKE_PREFIX_PATH = os.pathsep.join(CMAKE_PREFIX_PATH)
  245. environ = dict(os.environ)
  246. lines = []
  247. if not args.extend:
  248. lines += rollback_env_variables(environ, ENV_VAR_SUBFOLDERS)
  249. lines += prepend_env_variables(environ, ENV_VAR_SUBFOLDERS, CMAKE_PREFIX_PATH)
  250. lines += find_env_hooks(environ, CMAKE_PREFIX_PATH)
  251. print('\n'.join(lines))
  252. # need to explicitly flush the output
  253. sys.stdout.flush()
  254. except IOError as e:
  255. # and catch potential "broken pipe" if stdout is not writable
  256. # which can happen when piping the output to a file but the disk is full
  257. if e.errno == errno.EPIPE:
  258. print(e, file=sys.stderr)
  259. sys.exit(2)
  260. raise
  261. sys.exit(0)