#!/usr/bin/env python # 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 the Willow Garage 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. # # Revision $Id$ import os import sys import roslib.packages import roslib.scriptutil as s from subprocess import Popen, PIPE from optparse import OptionParser parser = OptionParser(usage="usage: %prog machine package(s)", prog='rossync') parser.add_option("-3", "--include-3rdparty", dest="thirdparty", default=False, action="store_true", help="include 3rdparty package in sync") parser.add_option("-n", "--dry-run", dest="dryrun", default=False, action="store_true", help="dry run; print what would be done") parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="verbose output") parser.add_option("-s", "--no-svn", dest="nosvn", default=False, action="store_true", help="don't include .svn directories in intermediate directories") parser.add_option("-f", "--force", dest="force", default=False, action="store_true", help="overwrite files on the remote machine, even if they are newer than those on the local machine (DANGEROUS)") parser.add_option("-p", "--progress", dest="progress", default=False, action="store_true", help="show progress during rsync (same as --progress option to rsync)") parser.add_option("-d", "--no-deps", dest="nodeps", default=False, action="store_true", help="sync only the named packages, without their dependencies") options, args = parser.parse_args() # TODO: Expose a convenient way to extend and/or replace this pattern list ignore_patterns = ['.*.swp', '.svn/lock'] if len(args) < 2: parser.error("please specify a machine and target package(s)") machine = args[0] targets = args[1:] # Does the user have rsync installed? command = ['which', 'rsync'] res = Popen(command, stdout=PIPE).communicate()[0] if res == '': print 'ERROR: Cannot execute rsync. Please install it.' print ' On Ubuntu / Debian systems:' print ' sudo apt-get install rsync' sys.exit(-1) # load up the package directory cache roslib.packages.list_pkgs() # Gather the roots that we may need to create on the other side roots = [os.environ['ROS_ROOT']] for d in os.environ['ROS_PACKAGE_PATH'].split(':'): if d != '': roots.append(d) if options.nodeps: pkgs = targets pkgs = list(set(pkgs)) #uniq pkg_dirs = [roslib.packages.get_pkg_dir(p) for p in pkgs] else: print "[rossync] Computing dependencies" # Start with packages that everyone needs, but noone depends on pkgs = ['rospack', 'rosbash', 'rostopic', 'rosout', 'mk', 'rosbuild', 'rosviz', 'rosparam'] # For now we must have all client libraries installed, because rosbuild # fails if it doesn't find any of them; this will be fixed. pkgs.extend(['roscpp', 'rospy', 'roslisp', 'rosoct']) for t in targets: pkgs.append(t) pkgs.extend(s.rospack_depends(t)) pkgs = list(set(pkgs)) #uniq pkg_dirs = [roslib.packages.get_pkg_dir(p) for p in pkgs] # Also throw in non-package directories that everyone needs pkg_dirs.extend([os.path.join(os.environ['ROS_ROOT'],'config'), os.path.join(os.environ['ROS_ROOT'],'bin')]) sync_dirs = [] dir_map = {} for p in pkg_dirs: # exclude thirdparty by default if not options.thirdparty and '3rdparty' in p: if options.verbose: print "[rossync] Excluding thirdparty package", p continue sync_dirs.append(p) newp = None for r in roots: nr = os.path.normpath(r) # Should really use os.path facilities here, but they don't seem to # offer a convenient way to split a path into a list of its component # pieces. So for now we'll be UNIX-specific. if '/'.join(p.split('/')[0:len(nr.split('/'))]) == nr: newp = ['/'.join(p.split('/')[len(nr.split('/')):])] if not options.nosvn: d = os.path.dirname(p) while len(d) >= len(nr): svn = os.path.join(d,'.svn') if os.path.exists(svn): newsvn = '/'.join(svn.split('/')[len(nr.split('/')):]) newp.append(newsvn) sync_dirs.append(newsvn) d = os.path.dirname(d) break if newp is None: print '[rossync] Failed to find valid root path for %s. Aborting' % (p) sys.exit(-1) if nr in dir_map: dir_map[nr].extend(newp) else: dir_map[nr] = newp print "[rossync] Creating the following directories on %s" %(machine) for r in roots: print "[rossync] %s"%(r) command = ['ssh', machine, 'mkdir', '-p', r] command = ' '.join(command) if options.verbose: print "[rossync] Executing command", command if not options.dryrun: Popen(command, shell=True).communicate()[0] if options.verbose: print "[rossync] Will sync %d directories: "%len(sync_dirs)+' '.join(sync_dirs) else: print "[rossync] Will sync %d directories"%len(sync_dirs) print "[rossync] This will require %d rsync command(s)"%len(dir_map.keys()) for parent_dir in dir_map: if options.verbose: print '[rossync] Changing directory to %s' % (parent_dir) os.chdir(parent_dir) command = ['rsync'] if options.force: command.append('-aRze') else: command.append('-aRuze') command.append('ssh') if options.progress: command.append('--progress') for p in ignore_patterns: command.append('--exclude=%s'%(p)) command = command + dir_map[parent_dir] + ["%s:%s"%(machine, parent_dir)] command = ' '.join(command) if options.verbose: print "[rossync] Executing command", command else: print "[rossync] Syncing %s"%(parent_dir) if not options.dryrun: Popen(command, shell=True).communicate()[0] print 'Done.'