Skip to content

Commit

Permalink
defer searching $PATH for vcexe until later; better errors
Browse files Browse the repository at this point in the history
Before this change, a missing vcexe would cause something like

    exceptions.RuntimeError: Couldn't find executable for 'hg'

With this change, a missing vcexe gives something like

    could not find 'hg'
    PATH is 'blah blah blah'
    program finished with exit code -1

Fixes #783
  • Loading branch information
Dustin J. Mitchell committed Sep 5, 2010
1 parent 0a0f3eb commit 838f9dd
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 35 deletions.
15 changes: 15 additions & 0 deletions slave/buildslave/commands/base.py
@@ -1,6 +1,7 @@

import os, signal, types, re, traceback
from stat import ST_CTIME, ST_MTIME, ST_SIZE
import os
import sys
import shutil

Expand Down Expand Up @@ -230,9 +231,23 @@ def setup(self, args):
self.timeout = args.get('timeout', 120)
self.maxTime = args.get('maxTime', None)
self.retry = args.get('retry')
self._commandPaths = {}
# VC-specific subclasses should override this to extract more args.
# Make sure to upcall!

def getCommand(self, name):
"""Wrapper around utils.getCommand that will output a resonable
error message and raise AbandonChain if the command cannot be
found"""
if name not in self._commandPaths:
try:
self._commandPaths[name] = utils.getCommand(name)
except RuntimeError:
self.sendStatus({'stderr' : "could not find '%s'\n" % name})
self.sendStatus({'stderr' : "PATH is '%s'\n" % os.environ.get('PATH', '')})
raise AbandonChain(-1)
return self._commandPaths[name]

def start(self):
self.sendStatus({'header': "starting " + self.header + "\n"})
self.command = None
Expand Down
10 changes: 6 additions & 4 deletions slave/buildslave/commands/bk.py
Expand Up @@ -18,7 +18,6 @@ class BK(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("bk")
self.bkurl = args['bkurl']
self.sourcedata = '"%s\n"' % self.bkurl

Expand All @@ -34,27 +33,29 @@ def sourcedirIsUpdateable(self):
self.srcdir, "BK/parent"))

def doVCUpdate(self):
bk = self.getCommand('bk')
revision = self.args['revision'] or 'HEAD'
# update: possible for mode in ('copy', 'update')
d = os.path.join(self.builder.basedir, self.srcdir)

# Revision is ignored since the BK free client doesn't support it.
command = [self.vcexe, 'pull']
command = [bk, 'pull']
c = runprocess.RunProcess(self.builder, command, d,
sendRC=False, timeout=self.timeout,
keepStdout=True, usePTY=False)
self.command = c
return c.start()

def doVCFull(self):
bk = self.getCommand('bk')

revision_arg = ''
if self.args['revision']:
revision_arg = "-r%s" % self.args['revision']

d = self.builder.basedir

command = [self.vcexe, 'clone', revision_arg] + self.bk_args + \
command = [bk, 'clone', revision_arg] + self.bk_args + \
[self.bkurl, self.srcdir]
c = runprocess.RunProcess(self.builder, command, d,
sendRC=False, timeout=self.timeout,
Expand All @@ -69,7 +70,8 @@ def getBKVersionCommand(self):
return: list of strings, passable as the command argument to RunProcess
"""
return [self.vcexe, "changes", "-r+", "-d:REV:"]
bk = self.getCommand('bk')
return [bk, "changes", "-r+", "-d:REV:"]

def parseGotRevision(self):
c = runprocess.RunProcess(self.builder,
Expand Down
22 changes: 14 additions & 8 deletions slave/buildslave/commands/bzr.py
Expand Up @@ -20,7 +20,6 @@ class Bzr(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("bzr")
self.repourl = args['repourl']
self.sourcedata = "%s\n" % self.repourl
self.revision = self.args.get('revision')
Expand All @@ -46,17 +45,20 @@ def cont(res):
return cont(None)

def doVCUpdate(self):
bzr = self.getCommand('bzr')
assert not self.revision
# update: possible for mode in ('copy', 'update')
srcdir = os.path.join(self.builder.basedir, self.srcdir)
command = [self.vcexe, 'update']
command = [bzr, 'update']
c = runprocess.RunProcess(self.builder, command, srcdir,
sendRC=False, timeout=self.timeout,
maxTime=self.maxTime, usePTY=False)
self.command = c
return c.start()

def doVCFull(self):
bzr = self.getCommand('bzr')

# checkout or export
d = self.builder.basedir
if self.mode == "export":
Expand All @@ -72,7 +74,7 @@ def doVCFull(self):
#
# So I won't bother using --lightweight for now.

command = [self.vcexe, 'checkout']
command = [bzr, 'checkout']
if self.revision:
command.append('--revision')
command.append(str(self.revision))
Expand All @@ -87,9 +89,10 @@ def doVCFull(self):
return d

def doVCExport(self):
bzr = self.getCommand('bzr')
tmpdir = os.path.join(self.builder.basedir, "export-temp")
srcdir = os.path.join(self.builder.basedir, self.srcdir)
command = [self.vcexe, 'checkout', '--lightweight']
command = [bzr, 'checkout', '--lightweight']
if self.revision:
command.append('--revision')
command.append(str(self.revision))
Expand All @@ -101,7 +104,7 @@ def doVCExport(self):
self.command = c
d = c.start()
def _export(res):
command = [self.vcexe, 'export', srcdir]
command = [bzr, 'export', srcdir]
c = runprocess.RunProcess(self.builder, command, tmpdir,
sendRC=False, timeout=self.timeout,
maxTime=self.maxTime, usePTY=False)
Expand All @@ -111,18 +114,20 @@ def _export(res):
return d

def doForceSharedRepo(self):
bzr = self.getCommand('bzr')

# Don't send stderr. When there is no shared repo, this might confuse
# users, as they will see a bzr error message. But having no shared
# repo is not an error, just an indication that we need to make one.
c = runprocess.RunProcess(self.builder, [self.vcexe, 'info', '.'],
c = runprocess.RunProcess(self.builder, [bzr, 'info', '.'],
self.builder.basedir,
sendStderr=False, sendRC=False, usePTY=False)
d = c.start()
def afterCheckSharedRepo(res):
if type(res) is int and res != 0:
log.msg("No shared repo found, creating it")
# bzr info fails, try to create shared repo.
c = runprocess.RunProcess(self.builder, [self.vcexe, 'init-repo', '.'],
c = runprocess.RunProcess(self.builder, [bzr, 'init-repo', '.'],
self.builder.basedir,
sendRC=False, usePTY=False)
self.command = c
Expand All @@ -145,7 +150,8 @@ def get_revision_number(self, out):
raise ValueError("unable to find revno: in bzr output: '%s'" % out)

def parseGotRevision(self):
command = [self.vcexe, "version-info"]
bzr = self.getCommand('bzr')
command = [bzr, "version-info"]
c = runprocess.RunProcess(self.builder, command,
os.path.join(self.builder.basedir, self.srcdir),
environ=self.env,
Expand Down
10 changes: 6 additions & 4 deletions slave/buildslave/commands/cvs.py
Expand Up @@ -26,7 +26,6 @@ class CVS(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("cvs")
self.cvsroot = args['cvsroot']
self.cvsmodule = args['cvsmodule']
self.global_options = args.get('global_options', [])
Expand All @@ -44,10 +43,11 @@ def sourcedirIsUpdateable(self):
self.srcdir, "CVS")))

def start(self):
cvs = self.getCommand("cvs")
if self.login is not None:
# need to do a 'cvs login' command first
d = self.builder.basedir
command = ([self.vcexe, '-d', self.cvsroot] + self.global_options
command = ([cvs, '-d', self.cvsroot] + self.global_options
+ ['login'])
c = runprocess.RunProcess(self.builder, command, d,
sendRC=False, timeout=self.timeout,
Expand All @@ -66,8 +66,9 @@ def _didLogin(self, res):
return SourceBaseCommand.start(self)

def doVCUpdate(self):
cvs = self.getCommand("cvs")
d = os.path.join(self.builder.basedir, self.srcdir)
command = [self.vcexe, '-z3'] + self.global_options + ['update', '-dP']
command = [cvs, '-z3'] + self.global_options + ['update', '-dP']
if self.branch:
command += ['-r', self.branch]
if self.revision:
Expand All @@ -79,12 +80,13 @@ def doVCUpdate(self):
return c.start()

def doVCFull(self):
cvs = self.getCommand("cvs")
d = self.builder.basedir
if self.mode == "export":
verb = "export"
else:
verb = "checkout"
command = ([self.vcexe, '-d', self.cvsroot, '-z3'] +
command = ([cvs, '-d', self.cvsroot, '-z3'] +
self.global_options +
[verb, '-d', self.srcdir])

Expand Down
11 changes: 7 additions & 4 deletions slave/buildslave/commands/darcs.py
Expand Up @@ -16,7 +16,6 @@ class Darcs(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("darcs")
self.repourl = args['repourl']
self.sourcedata = "%s\n" % self.repourl
self.revision = self.args.get('revision')
Expand All @@ -29,20 +28,22 @@ def sourcedirIsUpdateable(self):
self.srcdir, "_darcs")))

def doVCUpdate(self):
darcs = self.getCommand('darcs')
assert not self.revision
# update: possible for mode in ('copy', 'update')
d = os.path.join(self.builder.basedir, self.srcdir)
command = [self.vcexe, 'pull', '--all', '--verbose']
command = [darcs, 'pull', '--all', '--verbose']
c = runprocess.RunProcess(self.builder, command, d,
sendRC=False, timeout=self.timeout,
maxTime=self.maxTime, usePTY=False)
self.command = c
return c.start()

def doVCFull(self):
darcs = self.getCommand('darcs')
# checkout or export
d = self.builder.basedir
command = [self.vcexe, 'get', '--verbose', '--partial',
command = [darcs, 'get', '--verbose', '--partial',
'--repo-name', self.srcdir]
if self.revision:
# write the context to a file
Expand All @@ -69,8 +70,10 @@ def removeContextFile(self, res, n):
return res

def parseGotRevision(self):
darcs = self.getCommand('darcs')

# we use 'darcs context' to find out what we wound up with
command = [self.vcexe, "changes", "--context"]
command = [darcs, "changes", "--context"]
c = runprocess.RunProcess(self.builder, command,
os.path.join(self.builder.basedir, self.srcdir),
environ=self.env, timeout=self.timeout,
Expand Down
8 changes: 5 additions & 3 deletions slave/buildslave/commands/git.py
Expand Up @@ -23,7 +23,6 @@ class Git(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("git")
self.repourl = args['repourl']
self.branch = args.get('branch')
if not self.branch:
Expand All @@ -44,7 +43,8 @@ def sourcedirIsUpdateable(self):
return os.path.isdir(os.path.join(self._fullSrcdir(), ".git"))

def _dovccmd(self, command, cb=None, **kwargs):
c = runprocess.RunProcess(self.builder, [self.vcexe] + command, self._fullSrcdir(),
git = self.getCommand("git")
c = runprocess.RunProcess(self.builder, [git] + command, self._fullSrcdir(),
sendRC=False, timeout=self.timeout,
maxTime=self.maxTime, usePTY=False, **kwargs)
self.command = c
Expand Down Expand Up @@ -143,10 +143,12 @@ def _didInit(self, res):
return self.doVCUpdate()

def doVCFull(self):
git = self.getCommand("git")

# If they didn't ask for a specific revision, we can get away with a
# shallow clone.
if not self.args.get('revision') and self.args.get('shallow'):
cmd = [self.vcexe, 'clone', '--depth', '1', self.repourl,
cmd = [git, 'clone', '--depth', '1', self.repourl,
self._fullSrcdir()]
c = runprocess.RunProcess(self.builder, cmd, self.builder.basedir,
sendRC=False, timeout=self.timeout,
Expand Down
22 changes: 14 additions & 8 deletions slave/buildslave/commands/hg.py
Expand Up @@ -20,7 +20,6 @@ class Mercurial(SourceBaseCommand):

def setup(self, args):
SourceBaseCommand.setup(self, args)
self.vcexe = utils.getCommand("hg")
self.repourl = args['repourl']
self.clobberOnBranchChange = args.get('clobberOnBranchChange', True)
self.sourcedata = "%s\n" % self.repourl
Expand All @@ -34,8 +33,9 @@ def sourcedirIsUpdateable(self):
self.srcdir, ".hg"))

def doVCUpdate(self):
hg = self.getCommand('hg')
d = os.path.join(self.builder.basedir, self.srcdir)
command = [self.vcexe, 'pull', '--verbose', self.repourl]
command = [hg, 'pull', '--verbose', self.repourl]
c = runprocess.RunProcess(self.builder, command, d,
sendRC=False, timeout=self.timeout,
maxTime=self.maxTime, keepStdout=True, usePTY=False)
Expand All @@ -56,7 +56,8 @@ def _handleEmptyUpdate(self, res):
return res

def doVCFull(self):
command = [self.vcexe, 'clone', '--verbose', '--noupdate']
hg = self.getCommand('hg')
command = [hg, 'clone', '--verbose', '--noupdate']

# if got revision, clobbering and in dirname, only clone to specific revision
# (otherwise, do full clone to re-use .hg dir for subsequent builds)
Expand Down Expand Up @@ -87,8 +88,9 @@ def _vcfull(res):
return c

def _purge(self, dummy, dirname):
hg = self.getCommand('hg')
d = os.path.join(self.builder.basedir, self.srcdir)
purge = [self.vcexe, 'purge', '--all']
purge = [hg, 'purge', '--all']
purgeCmd = runprocess.RunProcess(self.builder, purge, d,
keepStdout=True, keepStderr=True, usePTY=False)

Expand All @@ -109,14 +111,15 @@ def _clobber(res):
return p

def _update(self, res):
hg = self.getCommand('hg')
if res != 0:
return res

# compare current branch to update
self.update_branch = self.args.get('branch', 'default')

d = os.path.join(self.builder.basedir, self.srcdir)
parentscmd = [self.vcexe, 'identify', '--num', '--branch']
parentscmd = [hg, 'identify', '--num', '--branch']
cmd = runprocess.RunProcess(self.builder, parentscmd, d,
sendRC=False, timeout=self.timeout, keepStdout=True,
keepStderr=True, usePTY=False)
Expand Down Expand Up @@ -170,7 +173,8 @@ def _parseIdentify(res):
return 0

def _checkRepoURL(res):
parentscmd = [self.vcexe, 'paths', 'default']
hg = self.getCommand('hg')
parentscmd = [hg, 'paths', 'default']
cmd2 = runprocess.RunProcess(self.builder, parentscmd, d,
keepStdout=True, keepStderr=True, usePTY=False,
timeout=self.timeout, sendRC=False)
Expand Down Expand Up @@ -235,7 +239,8 @@ def _vcfull(res):
return c

def _update2(self, res):
updatecmd=[self.vcexe, 'update', '--clean', '--repository', self.srcdir]
hg = self.getCommand('hg')
updatecmd=[hg, 'update', '--clean', '--repository', self.srcdir]
if self.args.get('revision'):
updatecmd.extend(['--rev', self.args['revision']])
else:
Expand All @@ -246,8 +251,9 @@ def _update2(self, res):
return self.command.start()

def parseGotRevision(self):
hg = self.getCommand('hg')
# we use 'hg identify' to find out what we wound up with
command = [self.vcexe, "identify", "--id", "--debug"] # get full rev id
command = [hg, "identify", "--id", "--debug"] # get full rev id
c = runprocess.RunProcess(self.builder, command,
os.path.join(self.builder.basedir, self.srcdir),
environ=self.env, timeout=self.timeout,
Expand Down

0 comments on commit 838f9dd

Please sign in to comment.