from Project import Project
from compiler import CompilerSpec, prepare_shell_script_farm
import buildutil
from buildutil import make_dir, run_cmd, rm_files
import re, os, sys, time
RE_TIME = re.compile(r"""^real \s* (\d*\.\d*)\n
user \s* (\d*\.\d*)\n
sys \s* (\d*\.\d*)""",
re.VERBOSE | re.MULTILINE)
class TimeInfo:
"""A record of real, system, and user time."""
def __init__(self, real=None, system=None, user=None, include_server=None):
self.real = real
self.system = system
self.user = user
self.include_server = include_server
class Build:
"""A Build is a combination of a Project and CompilerSpec.
Note: when done with an object of this type, call its restore function;
otherwise PATH will remain changed to an inappropriate value.
"""
def __init__(self, project, compiler, n_repeats):
self.project = project
self.compiler = compiler
self.n_repeats = n_repeats
self.base_dir = os.path.join(os.getcwd(), "build", self.project.name,
self.compiler.name)
self.unpacked_dir = os.path.join(self.base_dir,
self.project.unpacked_subdir)
if self.project.build_subdir:
self.build_dir = os.path.join(self.unpacked_dir,
project.build_subdir)
else:
self.build_dir = self.unpacked_dir
self.log_dir = self.build_dir
self.configure_done = os.path.join(self.log_dir, "bench-configure.done")
def __repr__(self):
return "Build(%s, %s)" % (`self.project`, `self.compiler`)
def _run_cmd_with_redirect_farm(self, cmd):
"""Initialize shell script farm for given compiler,
augment PATH, and run cmd.
A shell script farm is a set of scripts for dispatching a
chosen compiler using distcc. For example, the 'cc' script may
contain the one line:
dist /usr/mine/gcc "$@"
"""
farm_dir = os.path.join(self.build_dir, 'build-cc-script-farm')
make_dir(farm_dir)
print ("** Creating masquerading shell scripts in '%s'" % farm_dir)
masquerade = os.path.join(self.build_dir, 'masquerade')
prepare_shell_script_farm(self.compiler, farm_dir, masquerade)
old_path = os.environ['PATH']
try:
os.environ['PATH'] = farm_dir + ":" + old_path
return run_cmd(cmd)
finally:
os.environ['PATH'] = old_path
def unpack(self):
"""Unpack from source tarball into build directory"""
if re.search(r"\.tar\.bz2$", self.project.package_file):
tar_fmt = "tar xf %s --bzip2"
else:
tar_fmt = "tar xfz %s"
tar_cmd = tar_fmt % os.path.join(os.getcwd(), self.project.package_dir,
self.project.package_file)
make_dir(self.base_dir)
print "** Unpacking..."
run_cmd("cd %s && %s" % (self.base_dir, tar_cmd))
def configure(self):
"""Run configuration command for this tree, if any."""
make_dir(self.log_dir)
configure_log = os.path.join(self.log_dir, "bench-configure.log")
distcc_log = os.path.join(self.log_dir, "bench-configure-distcc.log")
rm_files((configure_log, distcc_log, self.configure_done))
make_dir(self.build_dir)
print "** Configuring..."
cmd = ("cd %s && \\\nDISTCC_LOG='%s' \\\nCC='%s' \\\nCXX='%s' \\\n%s \\\n>%s 2>&1"
% (self.build_dir, distcc_log,
self.compiler.cc, self.compiler.cxx,
self.project.configure_cmd, configure_log))
self._run_cmd_with_redirect_farm(cmd)
open(self.configure_done, 'w').close()
@staticmethod
def _extract_time_info(log_file_name):
"""Open log file and look for output of 'time -p' and include server
time."""
log_file = open(log_file_name, 'r')
text = log_file.read()
log_file.close()
match = RE_TIME.search(text)
if not match:
sys.exit('Could not locate time information in log %s.'
% log_file_name)
time_info = TimeInfo(float(match.group(1)),
float(match.group(2)),
float(match.group(3)))
lines = text.splitlines()
for line in lines:
if line.startswith('Include server timing. '):
is_time = float(
line[len('Include server timing. '):].split()[9][:-1])
time_info.include_server = is_time
break
return time_info
def did_configure(self):
"""Returns true if configure was successfully run for this
build in the past.
"""
return os.path.isfile(self.configure_done)
def build(self):
"""Actually build the package."""
build_log = os.path.join(self.log_dir, "bench-build.log")
prebuild_log = os.path.join(self.log_dir, "bench-prebuild.log")
distcc_log = os.path.join(self.log_dir, "bench-build-distcc.log")
rm_files((build_log, distcc_log))
make_dir(self.build_dir)
print "** Building..."
if self.project.pre_build_cmd:
cmd = ("cd %s && %s > %s 2>&1" % (self.build_dir,
self.project.pre_build_cmd,
prebuild_log))
self._run_cmd_with_redirect_farm(cmd)
distcc_hosts = buildutil.tweak_hosts(os.getenv("DISTCC_HOSTS"),
self.compiler.num_hosts,
self.compiler.host_opts)
cmd = ("cd %s && \\\n"
"(time -p \\\n"
"DISTCC_HOSTS='%s' \\\n"
"INCLUDE_SERVER_ARGS='-t --unsafe_absolute_includes %s' \\\n"
"%s%s \\\nDISTCC_LOG='%s' \\\nCC='%s' \\\nCXX='%s' "
"\\\n%s)"
"\\\n>%s 2>&1"
%
(self.build_dir,
distcc_hosts,
self.project.include_server_args,
self.compiler.pump_cmd,
self.project.build_cmd,
distcc_log,
self.compiler.cc,
self.compiler.cxx,
self.compiler.make_opts,
build_log))
result, unused_elapsed = self._run_cmd_with_redirect_farm(cmd)
return (result, Build._extract_time_info(build_log))
def clean(self):
clean_log = os.path.join(self.log_dir, "bench-clean.log")
make_dir(self.build_dir)
print "** Cleaning build directory"
cmd = "cd %s && make clean >%s 2>&1" % (self.build_dir, clean_log)
self._run_cmd_with_redirect_farm(cmd)
def scrub(self):
print "** Removing build directory"
rm_files((self.configure_done, ))
run_cmd("rm -rf %s" % self.unpacked_dir)
def build_actions(self, actions, summary):
"""Carry out selected actions.
Catch exceptions and handle."""
try:
time_info_accumulator = []
if 'sweep' in actions:
self.scrub()
if 'unpack' in actions:
self.unpack()
if 'configure' in actions:
self.configure()
if 'clean' in actions:
self.clean()
for i in range(self.n_repeats):
if 'build' in actions:
(result, time_info) = self.build()
if result: time_info_accumulator = 'NON-ZERO STATUS'
elif isinstance(time_info_accumulator, list):
time_info_accumulator.append(time_info)
if 'clean' in actions:
self.clean()
if 'scrub' in actions:
self.scrub()
summary.store(self.project, self.compiler, time_info_accumulator)
except KeyboardInterrupt:
raise
except:
apply(sys.excepthook, sys.exc_info()) summary.store(self.project, self.compiler, 'FAIL WITH EXCEPTION')