2010-11-12 05:24:46 -08:00
|
|
|
#!/usr/bin/python
|
2010-11-13 04:42:13 -08:00
|
|
|
import sys, os, subprocess, glob, time, random
|
2010-11-16 04:13:17 -08:00
|
|
|
import options, jwack, atoi
|
2010-11-12 05:24:46 -08:00
|
|
|
|
|
|
|
|
optspec = """
|
|
|
|
|
redo [targets...]
|
|
|
|
|
--
|
2010-11-13 04:36:44 -08:00
|
|
|
j,jobs= maximum number of jobs to build at once
|
2010-11-12 07:03:06 -08:00
|
|
|
d,debug print dependency checks as they happen
|
2010-11-12 07:10:55 -08:00
|
|
|
v,verbose print commands as they are run
|
2010-11-16 00:14:57 -08:00
|
|
|
shuffle randomize the build order to find dependency bugs
|
2010-11-12 05:24:46 -08:00
|
|
|
"""
|
|
|
|
|
o = options.Options('redo', optspec)
|
|
|
|
|
(opt, flags, extra) = o.parse(sys.argv[1:])
|
|
|
|
|
|
2010-11-13 01:40:37 -08:00
|
|
|
targets = extra or ['all']
|
2010-11-12 05:24:46 -08:00
|
|
|
|
2010-11-13 00:53:55 -08:00
|
|
|
if opt.debug:
|
2010-11-16 04:13:17 -08:00
|
|
|
os.environ['REDO_DEBUG'] = str(opt.debug or 0)
|
2010-11-13 00:53:55 -08:00
|
|
|
if opt.verbose:
|
|
|
|
|
os.environ['REDO_VERBOSE'] = '1'
|
2010-11-16 00:14:57 -08:00
|
|
|
if opt.shuffle:
|
|
|
|
|
os.environ['REDO_SHUFFLE'] = '1'
|
2010-11-13 00:53:55 -08:00
|
|
|
|
|
|
|
|
if not os.environ.get('REDO_BASE', ''):
|
|
|
|
|
base = os.path.commonprefix([os.path.abspath(os.path.dirname(t))
|
|
|
|
|
for t in targets] + [os.getcwd()])
|
|
|
|
|
bsplit = base.split('/')
|
|
|
|
|
for i in range(len(bsplit)-1, 0, -1):
|
|
|
|
|
newbase = '%s/.redo' % '/'.join(bsplit[:i])
|
|
|
|
|
if os.path.exists(newbase):
|
|
|
|
|
base = newbase
|
|
|
|
|
break
|
|
|
|
|
os.environ['REDO_BASE'] = base
|
2010-11-13 01:55:07 -08:00
|
|
|
os.environ['REDO_STARTDIR'] = os.getcwd()
|
2010-11-15 20:39:34 -08:00
|
|
|
os.environ['REDO'] = os.path.abspath(sys.argv[0])
|
2010-11-13 00:53:55 -08:00
|
|
|
|
2010-11-13 02:47:54 -08:00
|
|
|
# FIXME: just wiping out all the locks is kind of cheating. But we
|
|
|
|
|
# only do this from the toplevel redo process, so unless the user
|
|
|
|
|
# deliberately starts more than one redo on the same repository, it's
|
|
|
|
|
# sort of ok.
|
2010-11-13 04:36:44 -08:00
|
|
|
for f in glob.glob('%s/.redo/lock^*' % base):
|
|
|
|
|
os.unlink(f)
|
2010-11-13 02:47:54 -08:00
|
|
|
|
2010-11-16 01:59:32 -08:00
|
|
|
|
2010-11-13 00:53:55 -08:00
|
|
|
import vars
|
|
|
|
|
from helpers import *
|
|
|
|
|
|
2010-11-12 05:24:46 -08:00
|
|
|
|
2010-11-13 01:21:59 -08:00
|
|
|
class BuildError(Exception):
|
|
|
|
|
pass
|
2010-11-13 02:47:54 -08:00
|
|
|
class BuildLocked(Exception):
|
|
|
|
|
pass
|
2010-11-13 01:21:59 -08:00
|
|
|
|
|
|
|
|
|
2010-11-16 01:59:32 -08:00
|
|
|
def _possible_do_files(t):
|
|
|
|
|
yield "%s.do" % t, t, ''
|
|
|
|
|
dirname,filename = os.path.split(t)
|
|
|
|
|
l = filename.split('.')
|
|
|
|
|
l[0] = os.path.join(dirname, l[0])
|
|
|
|
|
for i in range(1,len(l)+1):
|
|
|
|
|
basename = '.'.join(l[:i])
|
|
|
|
|
ext = '.'.join(l[i:])
|
|
|
|
|
if ext: ext = '.' + ext
|
2010-11-16 04:16:01 -08:00
|
|
|
yield (os.path.join(dirname, "default%s.do" % ext),
|
|
|
|
|
os.path.join(dirname, basename), ext)
|
2010-11-16 01:59:32 -08:00
|
|
|
|
|
|
|
|
|
2010-11-12 05:24:46 -08:00
|
|
|
def find_do_file(t):
|
2010-11-16 01:59:32 -08:00
|
|
|
for dofile,basename,ext in _possible_do_files(t):
|
2010-11-16 04:13:17 -08:00
|
|
|
debug2('%s: %s ?\n' % (t, dofile))
|
2010-11-16 01:59:32 -08:00
|
|
|
if os.path.exists(dofile):
|
|
|
|
|
add_dep(t, 'm', dofile)
|
|
|
|
|
return dofile,basename,ext
|
|
|
|
|
else:
|
|
|
|
|
add_dep(t, 'c', dofile)
|
|
|
|
|
return None,None,None
|
2010-11-12 07:03:06 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def stamp(t):
|
2010-11-12 22:47:03 -08:00
|
|
|
stampfile = sname('stamp', t)
|
2010-11-13 00:45:49 -08:00
|
|
|
depfile = sname('dep', t)
|
2010-11-13 00:11:34 -08:00
|
|
|
if not os.path.exists(vars.BASE + '/.redo'):
|
2010-11-12 22:50:23 -08:00
|
|
|
# .redo might not exist in a 'make clean' target
|
|
|
|
|
return
|
2010-11-12 07:03:06 -08:00
|
|
|
open(stampfile, 'w').close()
|
2010-11-13 00:45:49 -08:00
|
|
|
open(depfile, 'a').close()
|
2010-11-12 07:03:06 -08:00
|
|
|
try:
|
|
|
|
|
mtime = os.stat(t).st_mtime
|
|
|
|
|
except OSError:
|
|
|
|
|
mtime = 0
|
|
|
|
|
os.utime(stampfile, (mtime, mtime))
|
|
|
|
|
|
2010-11-12 05:24:46 -08:00
|
|
|
|
2010-11-12 22:14:39 -08:00
|
|
|
def _preexec(t):
|
2010-11-12 23:14:02 -08:00
|
|
|
os.environ['REDO_TARGET'] = t
|
2010-11-13 00:11:34 -08:00
|
|
|
os.environ['REDO_DEPTH'] = vars.DEPTH + ' '
|
2010-11-12 23:14:58 -08:00
|
|
|
dn = os.path.dirname(t)
|
|
|
|
|
if dn:
|
|
|
|
|
os.chdir(dn)
|
2010-11-12 22:14:39 -08:00
|
|
|
|
|
|
|
|
|
2010-11-13 02:47:54 -08:00
|
|
|
def _build(t):
|
If foo and foo.do exist, then foo is always a target, not a source file.
The problem is if someone accidentally creates a file called "test" *before*
.redo/gen^test got created, then 'redo test' would do nothing, because redo
would assume it's a source file instead of a destination, according to djb's
rule. But in this case, we know it's not, since test.do exists, so let's
build it anyway. The problem is related to .PHONY rules in make.
This workaround is kind of cheating, because we can't safely apply that rule
if foo and default.do exist, even though default.do can be used to build
foo.
This probably won't happen very often... except with minimal/do, which
creates these empty files even when it shouldn't. I'm not sure if I should
try to fix that or not, though.
2010-11-16 03:40:13 -08:00
|
|
|
if (os.path.exists(t) and not os.path.exists(sname('gen', t))
|
|
|
|
|
and not os.path.exists('%s.do' % t)):
|
2010-11-16 01:59:32 -08:00
|
|
|
# an existing source file that is not marked as a generated file.
|
|
|
|
|
# This step is mentioned by djb in his notes. It turns out to be
|
|
|
|
|
# important to prevent infinite recursion. For example, a rule
|
|
|
|
|
# called default.o.do could be used to try to produce hello.c.o,
|
|
|
|
|
# which is stupid since hello.c is a static file.
|
|
|
|
|
stamp(t)
|
|
|
|
|
return # success
|
2010-11-12 22:47:03 -08:00
|
|
|
unlink(sname('dep', t))
|
|
|
|
|
open(sname('dep', t), 'w').close()
|
2010-11-16 01:59:32 -08:00
|
|
|
open(sname('gen', t), 'w').close() # it's definitely a generated file
|
|
|
|
|
(dofile, basename, ext) = find_do_file(t)
|
2010-11-12 05:24:46 -08:00
|
|
|
if not dofile:
|
2010-11-16 01:59:32 -08:00
|
|
|
raise BuildError('no rule to make %r' % t)
|
2010-11-13 00:45:49 -08:00
|
|
|
stamp(dofile)
|
2010-11-12 05:24:46 -08:00
|
|
|
unlink(t)
|
|
|
|
|
tmpname = '%s.redo.tmp' % t
|
|
|
|
|
unlink(tmpname)
|
|
|
|
|
f = open(tmpname, 'w+')
|
2010-11-16 01:59:32 -08:00
|
|
|
|
|
|
|
|
# this will run in the dofile's directory, so use only basenames here
|
2010-11-15 20:46:09 -08:00
|
|
|
argv = ['sh', '-e',
|
2010-11-13 01:29:27 -08:00
|
|
|
os.path.basename(dofile),
|
2010-11-16 01:59:32 -08:00
|
|
|
os.path.basename(basename), # target name (extension removed)
|
|
|
|
|
ext, # extension (if any), including leading dot
|
|
|
|
|
os.path.basename(tmpname) # randomized output file name
|
|
|
|
|
]
|
2010-11-13 00:11:34 -08:00
|
|
|
if vars.VERBOSE:
|
2010-11-12 07:10:55 -08:00
|
|
|
argv[1] += 'v'
|
2010-11-13 01:55:07 -08:00
|
|
|
log('%s\n' % relpath(t, vars.STARTDIR))
|
2010-11-12 22:14:39 -08:00
|
|
|
rv = subprocess.call(argv, preexec_fn=lambda: _preexec(t),
|
|
|
|
|
stdout=f.fileno())
|
2010-11-12 05:24:46 -08:00
|
|
|
st = os.stat(tmpname)
|
2010-11-12 22:47:03 -08:00
|
|
|
stampfile = sname('stamp', t)
|
2010-11-12 07:03:06 -08:00
|
|
|
if rv==0:
|
|
|
|
|
if st.st_size:
|
|
|
|
|
os.rename(tmpname, t)
|
|
|
|
|
else:
|
|
|
|
|
unlink(tmpname)
|
|
|
|
|
stamp(t)
|
2010-11-12 05:24:46 -08:00
|
|
|
else:
|
|
|
|
|
unlink(tmpname)
|
2010-11-12 07:03:06 -08:00
|
|
|
unlink(stampfile)
|
2010-11-12 05:24:46 -08:00
|
|
|
f.close()
|
|
|
|
|
if rv != 0:
|
2010-11-13 01:21:59 -08:00
|
|
|
raise BuildError('%s: exit code %d' % (t,rv))
|
2010-11-12 05:24:46 -08:00
|
|
|
|
2010-11-12 07:03:06 -08:00
|
|
|
|
2010-11-13 02:47:54 -08:00
|
|
|
def build(t):
|
2010-11-13 04:36:44 -08:00
|
|
|
mkdirp('%s/.redo' % vars.BASE)
|
2010-11-13 02:47:54 -08:00
|
|
|
lockname = sname('lock', t)
|
|
|
|
|
try:
|
|
|
|
|
fd = os.open(lockname, os.O_CREAT|os.O_EXCL)
|
|
|
|
|
except OSError, e:
|
|
|
|
|
if e.errno == errno.EEXIST:
|
|
|
|
|
log('%s (locked...)\n' % relpath(t, vars.STARTDIR))
|
2010-11-13 04:36:44 -08:00
|
|
|
os._exit(199)
|
2010-11-13 02:47:54 -08:00
|
|
|
else:
|
|
|
|
|
raise
|
|
|
|
|
os.close(fd)
|
|
|
|
|
try:
|
2010-11-13 04:36:44 -08:00
|
|
|
try:
|
|
|
|
|
return _build(t)
|
|
|
|
|
except BuildError, e:
|
|
|
|
|
err('%s\n' % e)
|
2010-11-13 02:47:54 -08:00
|
|
|
finally:
|
|
|
|
|
unlink(lockname)
|
2010-11-16 04:06:26 -08:00
|
|
|
os._exit(1)
|
2010-11-13 02:47:54 -08:00
|
|
|
|
|
|
|
|
|
2010-11-13 04:36:44 -08:00
|
|
|
def main():
|
2010-11-13 02:09:42 -08:00
|
|
|
retcode = 0
|
2010-11-13 02:47:54 -08:00
|
|
|
locked = {}
|
2010-11-13 04:36:44 -08:00
|
|
|
waits = {}
|
2010-11-16 00:14:57 -08:00
|
|
|
if vars.SHUFFLE:
|
|
|
|
|
random.shuffle(targets)
|
2010-11-13 02:09:42 -08:00
|
|
|
for t in targets:
|
|
|
|
|
if os.path.exists('%s/all.do' % t):
|
|
|
|
|
# t is a directory, but it has a default target
|
|
|
|
|
t = '%s/all' % t
|
2010-11-13 04:36:44 -08:00
|
|
|
waits[t] = jwack.start_job(t, lambda: build(t))
|
|
|
|
|
jwack.wait_all()
|
|
|
|
|
for t,pd in waits.items():
|
|
|
|
|
assert(pd.rv != None)
|
|
|
|
|
if pd.rv == 199:
|
|
|
|
|
# target was locked
|
2010-11-13 02:47:54 -08:00
|
|
|
locked[t] = 1
|
2010-11-13 04:36:44 -08:00
|
|
|
elif pd.rv:
|
|
|
|
|
err('%s: exit code was %r\n' % (t, pd.rv))
|
|
|
|
|
retcode = 1
|
2010-11-13 02:47:54 -08:00
|
|
|
while locked:
|
2010-11-13 04:36:44 -08:00
|
|
|
for t in locked.keys():
|
2010-11-13 02:47:54 -08:00
|
|
|
lockname = sname('lock', t)
|
|
|
|
|
stampname = sname('stamp', t)
|
|
|
|
|
if not os.path.exists(lockname):
|
|
|
|
|
relp = relpath(t, vars.STARTDIR)
|
|
|
|
|
log('%s (...unlocked!)\n' % relp)
|
|
|
|
|
if not os.path.exists(stampname):
|
|
|
|
|
err('%s: failed in another thread\n' % relp)
|
|
|
|
|
retcode = 2
|
2010-11-13 04:36:44 -08:00
|
|
|
del locked[t]
|
2010-11-13 02:47:54 -08:00
|
|
|
time.sleep(0.2)
|
2010-11-13 04:36:44 -08:00
|
|
|
return retcode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not vars.DEPTH:
|
|
|
|
|
# toplevel call to redo
|
|
|
|
|
exenames = [os.path.abspath(sys.argv[0]), os.path.realpath(sys.argv[0])]
|
|
|
|
|
if exenames[0] == exenames[1]:
|
|
|
|
|
exenames = [exenames[0]]
|
|
|
|
|
dirnames = [os.path.dirname(p) for p in exenames]
|
|
|
|
|
os.environ['PATH'] = ':'.join(dirnames) + ':' + os.environ['PATH']
|
|
|
|
|
|
|
|
|
|
try:
|
2010-11-16 04:13:17 -08:00
|
|
|
j = atoi.atoi(opt.jobs or 1)
|
2010-11-13 04:36:44 -08:00
|
|
|
if j < 1 or j > 1000:
|
|
|
|
|
err('invalid --jobs value: %r\n' % opt.jobs)
|
|
|
|
|
jwack.setup(j)
|
|
|
|
|
try:
|
|
|
|
|
retcode = main()
|
|
|
|
|
finally:
|
|
|
|
|
jwack.force_return_tokens()
|
|
|
|
|
if retcode:
|
|
|
|
|
err('exiting: %d\n' % retcode)
|
2010-11-13 02:09:42 -08:00
|
|
|
sys.exit(retcode)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
sys.exit(200)
|