config_elements.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2009, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 from __future__ import print_function
00034 import os
00035 import shutil
00036 import datetime
00037 
00038 from vcstools import VcsClient
00039 from vcstools.vcs_base import VcsError
00040 
00041 from common import samefile, MultiProjectException
00042 from config_yaml import PathSpec
00043 import ui
00044 
00045 # helper class
00046 
00047 class PreparationReport:
00048   """Specifies after user interaction of how to perform install / update"""
00049   def __init__(self, element):
00050     self.config_element = element
00051     self.abort = False # abort ALL operations
00052     self.skip = False # skip this tree
00053     self.error = None # message
00054     self.verbose = False # verbosity
00055     self.checkout = True # checkout vs update
00056     self.backup = False # backup vs delete
00057     self.backup_path = None # where to move tree to
00058 
00059 ## Each Config element provides actions on a local folder
00060     
00061 class ConfigElement:
00062   """ Base class for Config provides methods with not implemented
00063   exceptions.  Also a few shared methods."""
00064   def __init__(self, path, local_name, properties = None):
00065     self.path = path
00066     if path is None:
00067       raise MultiProjectException("Invalid empty path")
00068     self.local_name = local_name
00069     self.properties = properties
00070   def get_path(self):
00071     """A normalized absolute path"""
00072     return self.path
00073   def get_local_name(self):
00074     """What the user specified in his config"""
00075     return self.local_name
00076   def prepare_install(self, backup_path = None, arg_mode = 'abort', robust = False):
00077     """
00078     Check whether install can be performed, asking user for decision if necessary.
00079     
00080     :param arg_mode: one of prompt, backup, delete, skip. Determines how to handle error cases
00081     :param backup_path: if arg_mode==backup, determines where to backup to
00082     :param robust: if true, operation will be aborted without changes to the filesystem and without user interaction
00083     :returns: A preparation_report instance, telling whether to checkout or to update, how to deal with existing tree, and where to backup to.
00084     """
00085     preparation_report = PreparationReport(self)
00086     preparation_report.skip = True
00087     return preparation_report
00088   
00089   def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00090     """
00091     Attempt to make it so that self.path is the result of checking out / updating from remote repo.
00092     No user Interaction allowed here (for concurrent mode).
00093     
00094     :param checkout: whether to checkout or update
00095     :param backup: if checking out, what to do if path exists. If true, backup_path must be set.
00096     """
00097     raise NotImplementedError, "ConfigElement install unimplemented"
00098   
00099   def get_path_spec(self):
00100     """PathSpec object with values as specified in file"""
00101     raise NotImplementedError, "ConfigElement get_path_spec unimplemented"
00102   def get_properties(self):
00103     """Any meta information attached"""
00104     return self.properties
00105   def get_versioned_path_spec(self):
00106     """PathSpec where VCS elements have the version looked up"""
00107     raise NotImplementedError, "ConfigElement get_versioned_path_spec unimplemented"
00108   def is_vcs_element(self):
00109     # subclasses to override when appropriate
00110     return False
00111   def get_diff(self, basepath = None):
00112     raise NotImplementedError, "ConfigElement get_diff unimplemented"
00113   def get_status(self, basepath = None, untracked = False):
00114     raise NotImplementedError, "ConfigElement get_status unimplemented"
00115   def backup(self, backup_path):
00116     if not backup_path:
00117       raise MultiProjectException("[%s] Cannot install %s.  backup disabled."%(self.get_local_name(), self.get_path()))
00118     backup_path = os.path.join(backup_path, os.path.basename(self.path)+"_%s"%datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
00119     print("[%s] Backing up %s to %s"%(self.get_local_name(), self.get_path(), backup_path))
00120     shutil.move(self.path, backup_path)
00121   def __str__(self):
00122     return str(self.get_path_spec().get_legacy_yaml());
00123   def __eq__(self, other):
00124     if isinstance(other, self.__class__):
00125       return self.get_path_spec() == other.get_path_spec()
00126     else:
00127       return False
00128 
00129 
00130 class OtherConfigElement(ConfigElement):
00131 
00132   def __init__(self, path, local_name, uri=None, version='', properties = None):
00133     ConfigElement.__init__(self, path, local_name, properties)
00134     self.uri = uri
00135     self.version = version
00136 
00137   
00138   def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00139     return True
00140 
00141   def get_versioned_path_spec(self):
00142     raise MultiProjectException("Cannot generate versioned outputs with non source types")
00143 
00144   def get_path_spec(self):
00145     "yaml as from source"
00146     version = self.version
00147     if version == '': version = None
00148     return PathSpec(local_name = self.get_local_name(),
00149                     path = self.get_path(),
00150                     scmtype = None,
00151                     uri = self.uri,
00152                     version = version,
00153                     tags = self.get_properties())
00154 
00155 
00156 class SetupConfigElement(ConfigElement):
00157   """A setup config element specifies a single file containing configuration data for a config."""
00158 
00159   def install(self, checkout = True, backup = False, backup_path = None, verbose = False):
00160     return True
00161 
00162   def get_versioned_path_spec(self):
00163     raise MultiProjectException("Cannot generate versioned outputs with non source types")
00164   
00165   def get_path_spec(self):
00166     return PathSpec(local_name = self.get_local_name(), path = self.get_path(), tags=['setup-file'] + (self.get_properties() or []))
00167 
00168 
00169 class VCSConfigElement(ConfigElement):
00170   
00171   def __init__(self, path, local_name, uri, version='', properties = None):
00172     """
00173     Creates a config element for a VCS repository.
00174     
00175     :param path: absolute or relative path, str
00176     :param vcs_client: Object compatible with vcstools.VcsClientBase
00177     :param local_name: display name for the element, str
00178     :param uri: VCS uri to checkout/pull from, str
00179     :param version: optional revision spec (tagname, SHAID, ..., str)
00180     """
00181     ConfigElement.__init__(self, path, local_name, properties)
00182     if uri is None:
00183       raise MultiProjectException("Invalid scm entry having no uri attribute for path %s"%path)
00184     self.uri = uri.rstrip('/') # strip trailing slashes if defined to not be too strict #3061
00185     self.version = version
00186 
00187   def _get_vcsc(self):
00188     raise NotImplementedError, "VCSConfigElement _get_vcsc() unimplemented"
00189   
00190   def is_vcs_element(self):
00191     return True
00192 
00193   def get_vcs_type_name(self):
00194     # also see override in AVCSConfigElement
00195     return self._get_vcsc().get_vcs_type_name()
00196 
00197   def detect_presence(self):
00198     # also see override in AVCSConfigElement
00199     return self._get_vcsc().detect_presence()
00200 
00201   def path_exists(self):
00202     # we could use _get_vcsc().path_exit(), but this causes a time penalty for initializing
00203     # this is crucial for bash tab completion
00204     return os.path.isdir(self.path)
00205   
00206   def prepare_install(self, backup_path = None, arg_mode = 'abort', robust = False):
00207     preparation_report = PreparationReport(self)
00208     present = self.detect_presence()
00209     if present or self.path_exists():
00210       # Directory exists see what we need to do
00211       error_message = None
00212       
00213       if not present:
00214         error_message = "Failed to detect %s presence at %s."%(self.get_vcs_type_name(), self.path)
00215       else:
00216         cur_url = self._get_vcsc().get_url()
00217         if cur_url is not None:
00218           cur_url = cur_url.rstrip('/')  #strip trailing slashes for #3269
00219         if not cur_url or cur_url != self.uri.rstrip('/'):
00220           # local repositories get absolute pathnames
00221           if not (os.path.isdir(self.uri) and os.path.isdir(cur_url) and samefile(cur_url, self.uri)):
00222             error_message = "Url %s does not match %s requested."%(cur_url, self.uri)
00223       if error_message is None:
00224         # update should be possible
00225         preparation_report.checkout = False
00226       else:
00227         # If robust ala continue-on-error, just error now and it will be continued at a higher level
00228         if robust:
00229           raise MultiProjectException("Update Failed of %s: %s"%(self.path, error_message))
00230         # prompt the user based on the error code
00231         if arg_mode == 'prompt':
00232           print("Prepare updating %s (%s) to %s"%(self.uri, self.version, self.path))
00233           mode = ui.Ui.get_ui().prompt_del_abort_retry(error_message, allow_skip = True)
00234         else:
00235           mode = arg_mode
00236         if mode == 'backup':
00237             preparation_report.backup = True
00238             if backup_path == None:
00239               print("Prepare updating %s (%s) to %s"%(self.uri, self.version, self.path))
00240               preparation_report.backup_path = ui.Ui.get_ui().get_backup_path()
00241             else:
00242               preparation_report.backup_path = backup_path
00243         if mode == 'abort':
00244           preparation_report.abort = True
00245           preparation_report.error = error_message
00246         if mode == 'skip':
00247           preparation_report.skip = True
00248           preparation_report.error = error_message
00249         if mode == 'delete':
00250           preparation_report.backup = False
00251     return preparation_report
00252 
00253   def install(self, checkout = True, backup = True, backup_path = None, verbose = False):
00254     """
00255     Runs the equivalent of SCM checkout for new local repos or update for existing.
00256     
00257     :param checkout: whether to use an update command or a checkout/clone command
00258     :param backup: if checkout is True and folder exists, if backup is false folder will be DELETED.
00259     :param backup_path: if checkout is true and backup is true, move folder to this location
00260     """
00261     if checkout is True:
00262       print("[%s] Installing %s (%s) to %s"%(self.get_local_name(), self.uri, self.version, self.get_path()))
00263       if self.path_exists():
00264         if (backup is False):
00265           shutil.rmtree(self.path)
00266         else:
00267           self.backup(backup_path)
00268       if not self._get_vcsc().checkout(self.uri, self.version, verbose = verbose):
00269         raise MultiProjectException("[%s] Checkout of %s version %s into %s failed."%(self.get_local_name(), self.uri, self.version, self.get_path()))
00270     else:
00271       print("[%s] Updating %s"%(self.get_local_name(), self.get_path()))
00272       if not self._get_vcsc().update(self.version, verbose = verbose):
00273         raise MultiProjectException("[%s] Update Failed of %s"%(self.get_local_name(), self.get_path()))
00274     print("[%s] Done."%self.get_local_name())
00275           
00276   def get_path_spec(self):
00277     "yaml as from source"
00278     version = self.version
00279     if version == '': version = None
00280     return PathSpec(local_name = self.get_local_name(),
00281                     path = self.get_path(),
00282                     scmtype = self.get_vcs_type_name(),
00283                     uri = self.uri,
00284                     version = version,
00285                     tags = self.get_properties())
00286 
00287   def get_versioned_path_spec(self):
00288     "yaml looking up current version"
00289     version = self.version
00290     if version == '': version = None
00291     revision = None
00292     if version is not None:
00293       # revision is the UID of the version spec, can be them same
00294       revision = self._get_vcsc().get_version(self.version)
00295     currevision = self._get_vcsc().get_version()
00296     return PathSpec(local_name = self.get_local_name(),
00297                     path = self.get_path(),
00298                     scmtype = self.get_vcs_type_name(),
00299                     uri = self.uri,
00300                     version = version,
00301                     revision = revision,
00302                     currevision = currevision,
00303                     curr_uri = self._get_vcsc().get_url(),
00304                     tags = self.get_properties())
00305      
00306 
00307   def get_diff(self, basepath=None):
00308     return self._get_vcsc().get_diff(basepath)
00309   
00310   def get_status(self, basepath=None, untracked=False):
00311     return self._get_vcsc().get_status(basepath, untracked)
00312   
00313 
00314   
00315 class AVCSConfigElement(VCSConfigElement):
00316   """
00317   Implementation using vcstools vcsclient, works for types svn, git, hg, bzr, tar
00318   
00319   :raises: Lookup Exception for unknown types
00320   """
00321   def __init__(self, scmtype, path, local_name, uri, version = '', vcsc = None, properties = None):
00322     VCSConfigElement.__init__(self, path, local_name = local_name, uri = uri, version = version, properties = properties)
00323     self.vcsc = vcsc
00324     self._scmtype = scmtype
00325 
00326   def get_vcs_type_name(self):
00327     return self._scmtype
00328     
00329   def _get_vcsc(self):
00330     # lazy initializer
00331     if self.vcsc == None:
00332       try:
00333         self.vcsc = VcsClient(self._scmtype, self.get_path())
00334       except VcsError as e:
00335         raise MultiProjectException("Unable to create vcs client of type %s for %s: %s"%(self._scmtype, self.get_path(), e))
00336     return self.vcsc
00337 
00338   def detect_presence(self):
00339     # to make more use of lazy initializer, do not instantiate client for just this
00340     # this is crucial for bash tab completion
00341     if self.get_vcs_type_name() == 'git':
00342       return os.path.isdir(os.path.join(self.path, '.git'))
00343     elif self.get_vcs_type_name() == 'svn':
00344       return os.path.isdir(os.path.join(self.path, '.svn'))
00345     elif self.get_vcs_type_name() == 'hg':
00346       return os.path.isdir(os.path.join(self.path, '.hg'))
00347     elif self.get_vcs_type_name() == 'bzr':
00348       return os.path.isdir(os.path.join(self.path, '.bzr'))
00349     else:
00350       return self._get_vcsc().detect_presence()


win_rosinstall
Author(s): Daniel Stonier
autogenerated on Fri Jan 3 2014 12:16:33