from SmartLiveRebuild.output import out
+ """ A simple exception to be raised whenever the package is tied to
+ a specific revision. This exception is catched within the core,
+ and will not cause rebuild of the package. """
+ """ A base class for all VCS implementations, and which all VCS
+ classes should subclass, overriding appropriate methods
+ Subclasses should override reqenv and optenv in the first place.
+ These are iterables of environment variable names, which are
+ supposed to be grabbed from within the environment.bz2 file.
+ The reqenv attribute represents the obligatory variables; in
+ other words, a KeyError will be raised if any of them is unset.
+ The optenv attribute represents the optional variables; if one
+ of them is unset, an empty value will be used instead.
+ The callenv dictionary represents common environment variables
+ which are supposed to be set during subprocess execution.
+ The subclasses should append to this dictionary instead of
+ overriding it completely.
def __init__(self, cpv, bash, opts, settings):
+ """ Initialize the VCS class for package `cpv', storing it as
+ self.cpv. Call `bash' BashParser instance to get the values
+ for environment variables (self.reqenv + self.optenv).
+ `opts' should point to an ConfigValues instance, while
+ `settings' to the portage settings instance.
+ When subclassing, the __init__() function is a good place to
+ perform misc checks, like checking whether the package is
+ actually a live ebuild and thus not tied to a specific
+ For this particular task, use a code like below:
+ def __init__(self, *args):
+ VCSSupport.__init__(self, *args)
+ if self.env['SOME_REVISION']:
+ raise NonLiveEbuild('SOME_REVISION specifies revision, package is not really a live one')
self.env = bash(self.reqenv + self.optenv)
raise KeyError('Environment does not declare: %s' % missingvars)
def __call__(self, blocking = False):
+ """ Perform a single main loop iteration. """
+ """ Get the absolute path to the checkout directory. The program
+ will enter that particular directory before executing
+ the update command or calling one of the following methods:
raise NotImplementedError('VCS class needs to override getpath()')
+ """ Append the additional packages from another VCS class
+ instance `vcs'. This will be done whenever two packages
+ share the same checkout directory (as returned by getpath()).
if not isinstance(vcs, self.__class__):
raise ValueError('Unable to append %s to %s' % (vcs.__class__, self.__class__))
+ """ Return the revision saved by the eclass whenever the package
+ was built. This method should return the same type
+ of information as getrev() does, and should not rely on
+ anything besides self.env.
+ If a particular VCS eclass doesn't provide such
+ an information, don't override this method. If you fail to
+ grab the required information, return None.
+ """ Grab the revision from the work tree. """
raise NotImplementedError('VCS class needs to override getrev() or update()')
def revcmp(oldrev, newrev):
+ """ A revision comparison function, appropriate
+ for the particular type returned by getrev()
+ This functions should return True if two revisions
+ are equal (and thus no update is required), False otherwise.
def call(self, cmd, **kwargs):
+ """ A helper method for VCS classes. It executes the process
+ passed as `cmd' (in the form of a list), grabs it output
+ By default, STDERR is not captured (and thus is output to
+ screen), and the process is called with environment updated
+ from self.callenv. Additional keyword arguments will be
+ passed to subprocess.Popen().
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.callenv, **kwargs)
ret = p.communicate().decode(locale.getpreferredencoding(), 'replace')
+ """ Return the update command for a particular VCS as a shell
+ command string. It will be executed within the checkout
+ directory, as returned by self.getpath().
raise NotImplementedError('VCS class needs to override getupdatecmd()')
def diffstat(self, oldrev, newrev):
+ """ Execute the 'diffstat' command, summarizing the changes
+ between revisions `oldrev' and `newrev'.
+ If the VCS doesn't provide a standard diffstat command,
+ don't override this method.
+ """ Start the update process. Grabs the current revision from
+ the checkout (or getsavedrev()), grabs the update command
+ (self.getupdatecmd()) and executes it in the background
+ using subprocess.Popen().
+ The STDOUT of the new process will be forwarded to STDERR
+ to avoid polluting the package list with `--pretend'.
+ If one of the called processes is braindead and insists on
+ closing one of these descriptors, use `2>&1' to force
+ replicating them on shell level.
+ This function returns the spawned Popen() instance.
self.oldrev = (not self._opts.local_rev and self.getsavedrev()) or self.getrev()
def endupdate(self, blocking = False):
+ """ Depending on whether `blocking' is True, either wait
+ for update process termination or check whether it is done.
+ In the latter case, return None if the process is still
+ If the update command terminates successfully, this method
+ grabs the new working tree revision, compares it to the old
+ one and returns the comparison result as a boolean.
+ In other words, if the revision changed (and thus package
+ needs to be rebuilt), this method returns True and calls
+ self.diffstat() if appropriate. Otherwise, it returns False.
if self.subprocess is None:
raise Exception('update command returned non-zero result')
+ """ Terminate the running update subprocess if appropriate. """
if self._running and self.subprocess is not None:
+ """ Return the string used to identify the update process within
return ', '.join(self.cpv)