1#!/usr/bin/env python
2# Software License Agreement (BSD License)
3#
4# Copyright (c) 2010, Willow Garage, Inc.
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11#  * Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13#  * Redistributions in binary form must reproduce the above
14#    copyright notice, this list of conditions and the following
15#    disclaimer in the documentation and/or other materials provided
16#    with the distribution.
17#  * Neither the name of Willow Garage, Inc. nor the names of its
18#    contributors may be used to endorse or promote products derived
19#    from this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32# POSSIBILITY OF SUCH DAMAGE.
33
34# Author: kwc
35
36"""
37Library for locating ROS packages and stacks using the centralized
38index at ROS.org.
39"""
40
41from __future__ import print_function
42
43NAME = 'roslocate'
44
45import os
46import sys
47try:
48    from os import EX_USAGE
49except ImportError:
50    EX_USAGE = 0, 1, 2
51
52from optparse import OptionParser
53from rosinstall.locate import get_manifest, \
54     get_www, get_repo, get_vcs, get_vcs_uri_for_branch,\
55     get_rosinstall, InvalidData, BRANCH_RELEASE, BRANCH_DEVEL
56
57
58def options_to_branch(options):
59    # we don't let the user express the full range of options at the
60    # command-line, mainly for (1) simplicity and (2) the distro
61    # branch is not reliable.
62    if options.dev and options.rel:
63        sys.exit('only one of --dev and --rel can be used')
64    if options.dev:
65        return BRANCH_DEVEL
66    elif options.rel:
67        return BRANCH_RELEASE
68    return None
69
70def cmd_get_rosinstall(name, data, type_, options=None):
71    branch = options_to_branch(options)
72    prefix = options.prefix if options is not None and options.prefix else ''
73    return get_rosinstall(name, data, type_, branch, prefix)
74
75
76def get_type(name, data, type_, options=None):
77    return type_
78
79
80def cmd_get_vcs_uri(name, data, type_, options=None):
81    return get_vcs_uri_for_branch(data, options_to_branch(options))
82
83
84def cmd_get_vcs(name, data, type_, options=None):
85    return get_vcs(name, data, type_)
86
87
88def cmd_get_www(name, data, type_, options=None):
89    return get_www(name, data, type_)
90
91
92def get_description(name, data, type_, options=None):
93    if type_ == 'package':
94        return """
95Type: package
96Stack: %s
97Description: %s
98URL: %s
99    """ % (data.get('stack', 'none'), data.get('description', ''), data.get('url', ''))
100    elif type_ == 'stack':
101        return """
102Type: package
103Stack: %s
104Description: %s
105URL: %s
106    """ % (data.get('stack', 'none'), data.get('description', ''), data.get('url', ''))
107    else:
108        return """
109Type: %s
110Packages: %s
111Description: %s
112URL: %s
113    """ % (type_, ", ".join(data.get('packages', [])), data.get('description', ''), data.get('url', ''))
114
115
116def cmd_get_repo(name, data, type_, options=None):
117    return get_repo(name, data, type_)
118
119################################################################################
120
121# Bind library to commandline implementation
122
123
124def _fullusage(parser, error=EX_USAGE):
125    parser.print_help(file=sys.stderr)
126    sys.stderr.write("""
127Commands:
128  info\t\tGet rosinstall info of resource
129  vcs\t\tGet name of source control system
130  type\t\tPackage or stack
131  uri\t\tGet source control URI of resource
132  www\t\tGet web page of resource
133  repo\t\tGet repository name of resource
134  describe\tGet description of resource
135""")
136    sys.exit(error)
137
138_cmds = {
139    # info/rosinstall are now identical
140    'info': cmd_get_rosinstall,
141    'rosinstall': cmd_get_rosinstall, #alias
142    'vcs': cmd_get_vcs,
143    'type': get_type,
144    'uri': cmd_get_vcs_uri,
145    'repo': cmd_get_repo,
146    'www': cmd_get_www,
147    'describe': get_description,
148    'description': get_description,  # alias
149    }
150
151
152def roslocate_main():
153    args = sys.argv
154
155    parser = OptionParser(usage="usage: %prog <command> <resource> <options>", prog=NAME)
156
157    parser.add_option("--prefix",
158                      dest="prefix", default=False,
159                      metavar="PATH",
160                      help="path prefix for rosinstall")
161
162    # TODO: distro-specific return values
163    parser.add_option("--distro",
164                      dest="distro",
165                      help="fetch information for specific ROS distribution release (default: environment variable ROS_DISTRO if defined)")
166
167    # In this implementation, we're optimizing for the use case where
168    # the user wishes to do a source-based install of a released
169    # stack.  The user also has an efficient flag for specifying that
170    # they want a development branch instead.  We are not exposing
171    # users to the full range of devel/released/distro branches that
172    # the rosinstall file encodes, mainly because the distro branch is
173    # not reliable with DVCS systems like git.  Thus, rosinstall
174    # abstracts the logic for determining what the correct released
175    # branch to use is.
176    parser.add_option("--dev",
177                      dest="dev", default=False,
178                      action="store_true",
179                      help="fetch development branch information")
180    parser.add_option("--rel",
181                      dest="rel", default=False,
182                      action="store_true",
183                      help="fetch release branch information")
184
185    # parse command
186    if '-h' in args or '--help' in args:
187        # printing commands in OptionParser is a mess
188        _fullusage(parser, error=0)
189    if len(args) < 2:
190        _fullusage(parser)
191
192    # noop parse for now.  Will matter once we can pass in --distro
193    options, args = parser.parse_args()
194
195    cmd = args[0]
196    if not cmd in _cmds.keys():
197        _fullusage(parser)
198
199    if cmd not in ['info', 'rosinstall'] and options.prefix:
200        parser.error('--prefix only allowed with commands info, rosinstall')
201
202
203    if len(args) != 2:
204        parser.error("please provide a resource name (package or stack)")
205    name = args[1]
206
207    if not options.distro:
208        distro = os.environ['ROS_DISTRO'] if 'ROS_DISTRO' in os.environ else None
209        if distro:
210            print('Using ROS_DISTRO: %s' % distro, file=sys.stderr)
211            options.distro = distro
212        else:
213            parser.error("please provide the distro name with --distro DISTRO_NAME")
214
215    try:
216        data, type_, _ = get_manifest(name, options.distro)
217    except IOError:
218        sys.exit('cannot locate information about %s\n' % (name))
219
220    try:
221        print (_cmds[cmd](name, data, type_, options))
222        sys.stdout.flush() # raises correct error when used in a pipe
223    except InvalidData as e:
224        sys.stderr.write("%s\n" % e)
225    except IOError as ioe:
226        if ioe.errno == 32:
227            sys.exit(ioe)
228        raise
229
230if __name__ == '__main__':
231    roslocate_main()
232