File: setupfiles.py

package info (click to toggle)
ros-rosinstall 0.7.8-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 648 kB
  • sloc: python: 3,399; makefile: 131; xml: 12
file content (408 lines) | stat: -rw-r--r-- 13,863 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# Software License Agreement (BSD License)
#
# Copyright (c) 2009, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following
#    disclaimer in the documentation and/or other materials provided
#    with the distribution.
#  * Neither the name of Willow Garage, Inc. nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import os
import rosinstall.__version__

from rosinstall.helpers import ROSInstallException, get_ros_stack_path

# template for catkin fuerte, not valid for Groovy and beyond, to be
# removed once fuerte goes out of support
CATKIN_CMAKE_TOPLEVEL = """#
#  TOPLEVEL cmakelists
#
cmake_minimum_required(VERSION 2.8)
cmake_policy(SET CMP0003 NEW)
cmake_policy(SET CMP0011 NEW)

set(CMAKE_CXX_FLAGS_INIT "-Wall")

enable_testing()

include(${CMAKE_SOURCE_DIR}/workspace-config.cmake OPTIONAL)

list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/cmake)

file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

if (IS_DIRECTORY ${CMAKE_SOURCE_DIR}/catkin)
  message(STATUS "+++ catkin")
  set(CATKIN_BUILD_PROJECTS "ALL" CACHE STRING
    "List of projects to build, or ALL for all.  Use to completely exclude certain projects from cmake traversal.")
  add_subdirectory(catkin)
else()
  find_package(catkin)
endif()

catkin_workspace()
"""

SHELL_HEADER = """# THIS IS AN AUTO-GENERATED FILE
# IT IS UNLIKELY YOU WANT TO EDIT THIS FILE BY HAND
# IF YOU WANT TO CHANGE THE ROS ENVIRONMENT VARIABLES
# USE THE rosinstall OR rosws TOOL INSTEAD.
# Generator version: %s
# see: http://www.ros.org/wiki/rosinstall
""" % rosinstall.__version__.version


def generate_catkin_cmake(path, catkinpp):
    with open(os.path.join(path, "CMakeLists.txt"), 'w') as cmake_file:
        cmake_file.write(CATKIN_CMAKE_TOPLEVEL)

    if catkinpp:
        with open(os.path.join(path, "workspace-config.cmake"), 'w') as config_file:
            config_file.write("set (CMAKE_PREFIX_PATH %s)" % catkinpp)


def generate_embedded_python():
    return """import sys
import os
import yaml

workspace_path = os.environ.get('ROS_WORKSPACE', os.path.abspath('.'))
filename = os.path.join(workspace_path, '.rosinstall')

if not os.path.isfile(filename):
  print('ERROR')
  sys.exit("There is no file at %s" % filename)

with open(filename, "r") as fhand:
  try:
    v = fhand.read();
  except Exception as e:
    print('ERROR')
    sys.exit("Failed to read file: %s %s " % (filename, str(e)))

try:
  y = yaml.load(v);
except Exception as e:
  print('ERROR')
  sys.exit("Invalid yaml in %s: %s " % (filename, str(e)))

if y is not None:

  # put all non-setupfile entries into ROS_PACKAGE_PATH
  paths = []
  for vdict in y:
    for k, v in vdict.items():
      if v is not None and k != "setup-file":
        path = os.path.join(workspace_path, v['local-name'])
        if not os.path.isfile(path):
          # add absolute path from workspace to relative paths
          paths.append(os.path.normpath(path))
        else:
          print('ERROR')
          sys.exit("ERROR: referenced path is a file, not a folder: %s" % path)
  output = ''
  # add paths in reverse order
  if len(paths) > 0:
    output += ':'.join(reversed(paths))

  # We also want to return the location of any setupfile elements
  output += 'ROSINSTALL_PATH_SETUPFILE_SEPARATOR'
  setupfile_paths = []
  for vdict in y:
    for k, v in vdict.items():
      if v is not None and k == "setup-file":
        path = os.path.join(workspace_path, v['local-name'])
        if not os.path.exists(path):
          print('ERROR')
          sys.exit("WARNING: referenced setupfile does not exist: %s" % path)
        elif os.path.isfile(path):
          setupfile_paths.append(path)
        else:
          print('ERROR')
          sys.exit("ERROR: referenced setupfile is a folder: %s" % path)
  output += ':'.join(setupfile_paths)

  # printing will store the result in the variable
  print(output)"""


def generate_setup_sh_text(workspacepath):
    '''
    generates the string that goes into setup.sh.

    Sadly we cannot infer the workspacepath from within the sourced
    file, previous hacks trying to determine it from the shell context
    all failed in corner cases.

    :param workspacepath: The path to the workspace
    '''

    pycode = generate_embedded_python()

    # overlay or standard
    text = """#!/usr/bin/env sh
%(header)s

# This setup.sh file has to parse .rosinstall file, and source similar
# setup.sh files recursively. In the course of recursion, shell
# variables get overwritten. This means that when returning from
# recursion, any variable may be in a different state

# These variables accumulate data through recursion and must only be
# reset and unset at the top level of recursion.

if [ x"$_ROSINSTALL_IN_RECURSION" != x"recurse" ] ; then
  # reset setupfile accumulator
  _SETUPFILES_ROSINSTALL=
  _ROS_PACKAGE_PATH_ROSINSTALL=
  # reset RPP before sourcing other setup files
  export ROS_PACKAGE_PATH=
fi

export ROS_WORKSPACE=%(wspath)s
if [ ! "$ROS_MASTER_URI" ] ; then export ROS_MASTER_URI=http://localhost:11311 ; fi
unset ROS_ROOT

unset _SETUP_SH_ERROR

# python script to read .rosinstall even when rosinstall is not installed
# this files parses the .rosinstall and sets environment variables accordingly
# The ROS_PACKAGE_PATH contains all elements in reversed order (for historic reasons)

# We store into _PARSED_CONFIG the result of python code,
# which is the ros_package_path and the list of setup_files to source
# Using python here to benefit of the pyyaml library
export _PARSED_CONFIG=`/usr/bin/env python3 << EOPYTHON

%(pycode)s
EOPYTHON`

if [ x"$_PARSED_CONFIG" = x"ERROR" ]; then
  echo 'Could not parse .rosinstall file' 1<&2
  _SETUP_SH_ERROR=1
fi

# using sed to split up ros_package_path and setupfile results
_ROS_PACKAGE_PATH_ROSINSTALL_NEW=`echo "$_PARSED_CONFIG" | sed 's,\(.*\)ROSINSTALL_PATH_SETUPFILE_SEPARATOR\(.*\),\\1,'`
if [ ! -z "$_ROS_PACKAGE_PATH_ROSINSTALL_NEW" ]; then
  if [ ! -z "$_ROS_PACKAGE_PATH_ROSINSTALL" ]; then
    export _ROS_PACKAGE_PATH_ROSINSTALL=$_ROS_PACKAGE_PATH_ROSINSTALL:$_ROS_PACKAGE_PATH_ROSINSTALL_NEW
  else
    export _ROS_PACKAGE_PATH_ROSINSTALL=$_ROS_PACKAGE_PATH_ROSINSTALL_NEW
  fi
fi

_SETUPFILES_ROSINSTALL_NEW=`echo "$_PARSED_CONFIG" | sed 's,\(.*\)'ROSINSTALL_PATH_SETUPFILE_SEPARATOR'\(.*\),\\2,'`
if [ ! -z "$_SETUPFILES_ROSINSTALL_NEW" ]; then
  if [ ! -z "$_SETUPFILES_ROSINSTALL" ]; then
    _SETUPFILES_ROSINSTALL=$_SETUPFILES_ROSINSTALL_NEW:$_SETUPFILES_ROSINSTALL
  else
    _SETUPFILES_ROSINSTALL=$_SETUPFILES_ROSINSTALL_NEW
  fi
fi
unset _PARSED_CONFIG

# colon separates entries
_LOOP_SETUP_FILE=`echo $_SETUPFILES_ROSINSTALL | sed 's,\([^:]*\)[:]\(.*\),\\1,'`
# this loop does fake recursion, as the called setup.sh may work on
# the remaining elements in the _SETUPFILES_ROSINSTALL stack
while [ ! -z "$_LOOP_SETUP_FILE" ]
do
  # need to pop from stack before recursing, as chained setup.sh might rely on this
  _SETUPFILES_ROSINSTALL=`echo $_SETUPFILES_ROSINSTALL | sed 's,\([^:]*[:]*\),,'`
  if [ -f "$_LOOP_SETUP_FILE" ]; then
    _ROSINSTALL_IN_RECURSION=recurse
    . $_LOOP_SETUP_FILE
    unset _ROSINSTALL_IN_RECURSION
  else
    echo warn: no such file : "$_LOOP_SETUP_FILE"
  fi
  _LOOP_SETUP_FILE=`echo $_SETUPFILES_ROSINSTALL | sed 's,\([^:]*\)[:]\(.*\),\\1,'`
done

unset _LOOP_SETUP_FILE
unset _SETUPFILES_ROSINSTALL

# prepend elements from .rosinstall file to ROS_PACKAGE_PATH
# ignoring duplicates entries from value set by setup files
export ROS_PACKAGE_PATH=`/usr/bin/env python3 << EOPYTHON
import os
ros_package_path = os.environ.get('ROS_PACKAGE_PATH', '')
original_elements = ros_package_path.split(':')
ros_package_path2 = os.environ.get('_ROS_PACKAGE_PATH_ROSINSTALL', '')
new_elements = ros_package_path2.split(':')
new_elements = [path for path in new_elements if path]

for original_path in original_elements:
  if original_path and original_path not in new_elements:
    new_elements.append(original_path)
print(':'.join(new_elements))
EOPYTHON`

unset _ROS_PACKAGE_PATH_ROSINSTALL

# restore ROS_WORKSPACE in case other setup.sh changed/unset it
export ROS_WORKSPACE=%(wspath)s

# if setup.sh did not set ROS_ROOT (pre-fuerte)
if [ -z "${ROS_ROOT}" ]; then
  # using ROS_ROOT now being in ROS_PACKAGE_PATH
  export _ROS_ROOT_ROSINSTALL=`/usr/bin/env python3 << EOPYTHON
import sys, os;
if 'ROS_PACKAGE_PATH' in os.environ:
  pkg_path = os.environ['ROS_PACKAGE_PATH']
  for path in pkg_path.split(':'):
    if (os.path.basename(path) == 'ros'
        and os.path.isfile(os.path.join(path, 'stack.xml'))):
      print(path)
      break
EOPYTHON`

  if [ ! -z "${_ROS_ROOT_ROSINSTALL}" ]; then
    export ROS_ROOT=$_ROS_ROOT_ROSINSTALL
    export PATH=$ROS_ROOT/bin:$PATH
    export PYTHONPATH=$ROS_ROOT/core/roslib/src:$PYTHONPATH
  fi
unset _ROS_ROOT_ROSINSTALL
fi

if [ -z "${ROS_ROOT}" ]; then
    export ROS_ROOT=/usr/share/ros
fi

if [ ! -z "$_SETUP_SH_ERROR" ]; then
  # return failure code when sourcing file
  false
fi
""" % {'header': SHELL_HEADER, 'wspath': workspacepath, 'pycode': pycode}

    return text


def generate_setup_bash_text(shell):
    '''
    Generates the contents that go into a setup.bash or setup.zsh
    file.  The intent of such a file is to enable shell extensions,
    such as special ros commands and tab completion.  The generation
    is complex because the setup of the system changed between ROS
    electric and fuerte. In fuerte, the distro setup.sh also loads
    distro rosbash based on CATKIN_SHELL. Before fuerte, it is up to
    setup.bash to do so.
    '''
    if shell == 'bash':
        script_path = """
SCRIPT_PATH="${BASH_SOURCE[0]}";
if([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
export OLDPWDBAK=$OLDPWD
pushd . > /dev/null
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null
export OLDPWD=$OLDPWDBAK
"""
        call_setup_sh = ". $SCRIPT_PATH/setup.sh"
    elif shell == 'zsh':
        script_path = 'SCRIPT_PATH="$(dirname $0)"'
        call_setup_sh = """
emulate sh # emulate POSIX
. $SCRIPT_PATH/setup.sh
emulate zsh # back in zsh
"""
    else:
        raise ROSInstallException("%s shell unsupported." % shell)

    text = """#!/usr/bin/env %(shell)s
%(header)s

CATKIN_SHELL=%(shell)s

%(script_path)s

# Load the path of this particular setup.%(shell)s

if [ ! -f "$SCRIPT_PATH/setup.sh" ]; then
  echo "Bug: shell script unable to determine its own location: $SCRIPT_PATH"
  return 22
fi

# unset _ros_decode_path (function of rosbash) to check later whether setup.sh has sourced ros%(shell)s
unset -f _ros_decode_path 1> /dev/null 2>&1

%(call_setup_sh)s

# if we have a ROS_ROOT, then we might need to source rosbash (pre-fuerte)
if [ ! -z "${ROS_ROOT}" ]; then
  # check whether setup.sh also already sourced rosbash
  # Cannot rely on $? due to set -o errexit in build scripts
  RETURNCODE=`type _ros_decode_path 2> /dev/null | grep function 1>/dev/null 2>&1 || echo error`

  # for ROS electric and before, source rosbash
  if [ ! "$RETURNCODE" = "" ]; then
    RETURNCODE=`rospack help 1> /dev/null 2>&1 || echo error`
    if  [ "$RETURNCODE" = "" ]; then
      ROSSHELL_PATH=`rospack find rosbash`/ros%(shell)s
      if [ -e "$ROSSHELL_PATH" ]; then
        . $ROSSHELL_PATH
      fi
    else
      echo "rospack could not be found, you cannot have ros%(shell)s features until you bootstrap ros"
    fi
  fi
fi
""" % {'shell': shell,
       'script_path': script_path,
       'call_setup_sh': call_setup_sh,
       'header': SHELL_HEADER}
    return text


def generate_setup(config, no_ros_allowed=False):
    ros_root = get_ros_stack_path(config)
    if ros_root is None:
        if not no_ros_allowed:
            candidates = []
            for t in config.get_config_elements():
                if os.path.basename(t.get_local_name()) == 'ros':
                    candidates.append(t.get_path())
            raise ROSInstallException("""
No 'ros' stack detected in candidates %s.
Please add the location of a ros distribution to this command.

See http://ros.org/wiki/rosinstall.""" % (candidates))

    text = generate_setup_sh_text(workspacepath=config.get_base_path())
    setup_path = os.path.join(config.get_base_path(), 'setup.sh')
    with open(setup_path, 'w') as fhand:
        fhand.write(text)

    for shell in ['bash', 'zsh']:
        text = generate_setup_bash_text(shell)
        setup_path = os.path.join(config.get_base_path(), 'setup.%s' % shell)
        with open(setup_path, 'w') as fhand:
            fhand.write(text)