refactor: convert build_process
to use BuildProcessInstaller
(#25167)
`build_process` has been around a long time but it's become a very large, unwieldy method. It's hard to work with because it has a lot of local variables that need to persist across all of the code. - [x] To address this, convert it its own `BuildInfoProcess` class. - [x] Start breaking the method apart by factoring out the main installation logic into its own function.
This commit is contained in:
parent
0a0338ddfa
commit
cf8d1b0387
1 changed files with 208 additions and 148 deletions
|
@ -144,14 +144,8 @@ def _handle_external_and_upstream(pkg, explicit):
|
||||||
|
|
||||||
|
|
||||||
def _do_fake_install(pkg):
|
def _do_fake_install(pkg):
|
||||||
|
"""Make a fake install directory with fake executables, headers, and libraries.
|
||||||
"""
|
"""
|
||||||
Make a fake install directory containing fake executables, headers,
|
|
||||||
and libraries.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pkg (spack.package.PackageBase): the package whose installation is to be faked
|
|
||||||
"""
|
|
||||||
|
|
||||||
command = pkg.name
|
command = pkg.name
|
||||||
header = pkg.name
|
header = pkg.name
|
||||||
library = pkg.name
|
library = pkg.name
|
||||||
|
@ -1170,8 +1164,7 @@ def _install_task(self, task):
|
||||||
# stop early from clients, and is not an error at this point
|
# stop early from clients, and is not an error at this point
|
||||||
pid = '{0}: '.format(pkg.pid) if tty.show_pid() else ''
|
pid = '{0}: '.format(pkg.pid) if tty.show_pid() else ''
|
||||||
tty.debug('{0}{1}'.format(pid, str(e)))
|
tty.debug('{0}{1}'.format(pid, str(e)))
|
||||||
tty.debug('Package stage directory: {0}'
|
tty.debug('Package stage directory: {0}' .format(pkg.stage.source_path))
|
||||||
.format(pkg.stage.source_path))
|
|
||||||
|
|
||||||
def _next_is_pri0(self):
|
def _next_is_pri0(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1678,66 +1671,126 @@ def install(self):
|
||||||
'reported errors for failing package(s).')
|
'reported errors for failing package(s).')
|
||||||
|
|
||||||
|
|
||||||
def build_process(pkg, kwargs):
|
class BuildProcessInstaller(object):
|
||||||
"""Perform the installation/build of the package.
|
"""This class implements the part installation that happens in the child process."""
|
||||||
|
|
||||||
This runs in a separate child process, and has its own process and
|
def __init__(self, pkg, install_args):
|
||||||
python module space set up by build_environment.start_build_process().
|
"""Create a new BuildProcessInstaller.
|
||||||
|
|
||||||
|
It is assumed that the lifecycle of this object is the same as the child
|
||||||
|
process in the build.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
pkg (spack.package.PackageBase) the package being installed.
|
||||||
|
install_args (dict) arguments to do_install() from parent process.
|
||||||
|
|
||||||
This function's return value is returned to the parent process.
|
|
||||||
"""
|
"""
|
||||||
fake = kwargs.get('fake', False)
|
self.pkg = pkg
|
||||||
install_source = kwargs.get('install_source', False)
|
|
||||||
keep_stage = kwargs.get('keep_stage', False)
|
|
||||||
skip_patch = kwargs.get('skip_patch', False)
|
|
||||||
unmodified_env = kwargs.get('unmodified_env', {})
|
|
||||||
verbose = kwargs.get('verbose', False)
|
|
||||||
|
|
||||||
timer = Timer()
|
# whether to do a fake install
|
||||||
|
self.fake = install_args.get('fake', False)
|
||||||
|
|
||||||
|
# whether to install source code with the packag
|
||||||
|
self.install_source = install_args.get('install_source', False)
|
||||||
|
|
||||||
|
# whether to keep the build stage after installation
|
||||||
|
self.keep_stage = install_args.get('keep_stage', False)
|
||||||
|
|
||||||
|
# whether to skip the patch phase
|
||||||
|
self.skip_patch = install_args.get('skip_patch', False)
|
||||||
|
|
||||||
|
# whether to enable echoing of build output initially or not
|
||||||
|
self.verbose = install_args.get('verbose', False)
|
||||||
|
|
||||||
|
# env before starting installation
|
||||||
|
self.unmodified_env = install_args.get('unmodified_env', {})
|
||||||
|
|
||||||
|
# timer for build phases
|
||||||
|
self.timer = Timer()
|
||||||
|
|
||||||
# If we are using a padded path, filter the output to compress padded paths
|
# If we are using a padded path, filter the output to compress padded paths
|
||||||
# The real log still has full-length paths.
|
# The real log still has full-length paths.
|
||||||
filter_padding = spack.config.get("config:install_tree:padded_length", None)
|
filter_padding = spack.config.get("config:install_tree:padded_length", None)
|
||||||
filter_fn = spack.util.path.padding_filter if filter_padding else None
|
self.filter_fn = spack.util.path.padding_filter if filter_padding else None
|
||||||
|
|
||||||
if not fake:
|
|
||||||
if not skip_patch:
|
|
||||||
pkg.do_patch()
|
|
||||||
else:
|
|
||||||
pkg.do_stage()
|
|
||||||
|
|
||||||
|
# info/debug information
|
||||||
pid = '{0}: '.format(pkg.pid) if tty.show_pid() else ''
|
pid = '{0}: '.format(pkg.pid) if tty.show_pid() else ''
|
||||||
pre = '{0}{1}:'.format(pid, pkg.name)
|
self.pre = '{0}{1}:'.format(pid, pkg.name)
|
||||||
pkg_id = package_id(pkg)
|
self.pkg_id = package_id(pkg)
|
||||||
|
|
||||||
tty.debug('{0} Building {1} [{2}]'
|
def run(self):
|
||||||
.format(pre, pkg_id, pkg.build_system_class))
|
"""Main entry point from ``build_process`` to kick off install in child."""
|
||||||
|
|
||||||
|
if not self.fake:
|
||||||
|
if not self.skip_patch:
|
||||||
|
self.pkg.do_patch()
|
||||||
|
else:
|
||||||
|
self.pkg.do_stage()
|
||||||
|
|
||||||
|
tty.debug(
|
||||||
|
'{0} Building {1} [{2}]' .format(
|
||||||
|
self.pre,
|
||||||
|
self.pkg_id,
|
||||||
|
self.pkg.build_system_class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# get verbosity from do_install() parameter or saved value
|
# get verbosity from do_install() parameter or saved value
|
||||||
echo = verbose
|
self.echo = self.verbose
|
||||||
if spack.package.PackageBase._verbose is not None:
|
if spack.package.PackageBase._verbose is not None:
|
||||||
echo = spack.package.PackageBase._verbose
|
self.echo = spack.package.PackageBase._verbose
|
||||||
|
|
||||||
pkg.stage.keep = keep_stage
|
self.pkg.stage.keep = self.keep_stage
|
||||||
|
|
||||||
with pkg.stage:
|
with self.pkg.stage:
|
||||||
# Run the pre-install hook in the child process after
|
# Run the pre-install hook in the child process after
|
||||||
# the directory is created.
|
# the directory is created.
|
||||||
spack.hooks.pre_install(pkg.spec)
|
spack.hooks.pre_install(self.pkg.spec)
|
||||||
if fake:
|
if self.fake:
|
||||||
_do_fake_install(pkg)
|
_do_fake_install(self.pkg)
|
||||||
else:
|
else:
|
||||||
source_path = pkg.stage.source_path
|
if self.install_source:
|
||||||
if install_source and os.path.isdir(source_path):
|
self._install_source()
|
||||||
src_target = os.path.join(pkg.spec.prefix, 'share',
|
|
||||||
pkg.name, 'src')
|
self._real_install()
|
||||||
tty.debug('{0} Copying source to {1}'
|
|
||||||
.format(pre, src_target))
|
# Stop the timer and save results
|
||||||
|
self.timer.stop()
|
||||||
|
with open(self.pkg.times_log_path, 'w') as timelog:
|
||||||
|
self.timer.write_json(timelog)
|
||||||
|
|
||||||
|
# Run post install hooks before build stage is removed.
|
||||||
|
spack.hooks.post_install(self.pkg.spec)
|
||||||
|
|
||||||
|
build_time = self.timer.total - self.pkg._fetch_time
|
||||||
|
tty.msg('{0} Successfully installed {1}'.format(self.pre, self.pkg_id),
|
||||||
|
'Fetch: {0}. Build: {1}. Total: {2}.'
|
||||||
|
.format(_hms(self.pkg._fetch_time), _hms(build_time),
|
||||||
|
_hms(self.timer.total)))
|
||||||
|
_print_installed_pkg(self.pkg.prefix)
|
||||||
|
|
||||||
|
# Send final status that install is successful
|
||||||
|
spack.hooks.on_install_success(self.pkg.spec)
|
||||||
|
|
||||||
|
# preserve verbosity across runs
|
||||||
|
return self.echo
|
||||||
|
|
||||||
|
def _install_source(self):
|
||||||
|
"""Install source code from stage into share/pkg/src if necessary."""
|
||||||
|
pkg = self.pkg
|
||||||
|
if not os.path.isdir(pkg.stage.source_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
src_target = os.path.join(pkg.spec.prefix, 'share', pkg.name, 'src')
|
||||||
|
tty.debug('{0} Copying source to {1}' .format(self.pre, src_target))
|
||||||
|
|
||||||
fs.install_tree(pkg.stage.source_path, src_target)
|
fs.install_tree(pkg.stage.source_path, src_target)
|
||||||
|
|
||||||
|
def _real_install(self):
|
||||||
|
pkg = self.pkg
|
||||||
|
|
||||||
# Do the real install in the source directory.
|
# Do the real install in the source directory.
|
||||||
with fs.working_dir(pkg.stage.source_path):
|
with fs.working_dir(pkg.stage.source_path):
|
||||||
|
|
||||||
# Save the build environment in a file before building.
|
# Save the build environment in a file before building.
|
||||||
dump_environment(pkg.env_path)
|
dump_environment(pkg.env_path)
|
||||||
|
|
||||||
|
@ -1772,21 +1825,29 @@ def build_process(pkg, kwargs):
|
||||||
try:
|
try:
|
||||||
# DEBUGGING TIP - to debug this section, insert an IPython
|
# DEBUGGING TIP - to debug this section, insert an IPython
|
||||||
# embed here, and run the sections below without log capture
|
# embed here, and run the sections below without log capture
|
||||||
with log_output(
|
log_contextmanager = log_output(
|
||||||
log_file, echo, True, env=unmodified_env,
|
log_file,
|
||||||
filter_fn=filter_fn
|
self.echo,
|
||||||
) as logger:
|
True,
|
||||||
|
env=self.unmodified_env,
|
||||||
|
filter_fn=self.filter_fn
|
||||||
|
)
|
||||||
|
|
||||||
|
with log_contextmanager as logger:
|
||||||
with logger.force_echo():
|
with logger.force_echo():
|
||||||
inner_debug_level = tty.debug_level()
|
inner_debug_level = tty.debug_level()
|
||||||
tty.set_debug(debug_level)
|
tty.set_debug(debug_level)
|
||||||
tty.msg("{0} Executing phase: '{1}'"
|
tty.msg(
|
||||||
.format(pre, phase_name))
|
"{0} Executing phase: '{1}'" .format(
|
||||||
|
self.pre,
|
||||||
|
phase_name
|
||||||
|
)
|
||||||
|
)
|
||||||
tty.set_debug(inner_debug_level)
|
tty.set_debug(inner_debug_level)
|
||||||
|
|
||||||
# Redirect stdout and stderr to daemon pipe
|
# Redirect stdout and stderr to daemon pipe
|
||||||
phase = getattr(pkg, phase_attr)
|
phase = getattr(pkg, phase_attr)
|
||||||
timer.phase(phase_name)
|
self.timer.phase(phase_name)
|
||||||
|
|
||||||
# Catch any errors to report to logging
|
# Catch any errors to report to logging
|
||||||
phase(pkg.spec, pkg.prefix)
|
phase(pkg.spec, pkg.prefix)
|
||||||
|
@ -1798,32 +1859,31 @@ def build_process(pkg, kwargs):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# We assume loggers share echo True/False
|
# We assume loggers share echo True/False
|
||||||
echo = logger.echo
|
self.echo = logger.echo
|
||||||
|
|
||||||
# After log, we can get all output/error files from the package stage
|
# After log, we can get all output/error files from the package stage
|
||||||
combine_phase_logs(pkg.phase_log_files, pkg.log_path)
|
combine_phase_logs(pkg.phase_log_files, pkg.log_path)
|
||||||
log(pkg)
|
log(pkg)
|
||||||
|
|
||||||
# Stop the timer and save results
|
|
||||||
timer.stop()
|
|
||||||
with open(pkg.times_log_path, 'w') as timelog:
|
|
||||||
timer.write_json(timelog)
|
|
||||||
|
|
||||||
# Run post install hooks before build stage is removed.
|
def build_process(pkg, install_args):
|
||||||
spack.hooks.post_install(pkg.spec)
|
"""Perform the installation/build of the package.
|
||||||
|
|
||||||
build_time = timer.total - pkg._fetch_time
|
This runs in a separate child process, and has its own process and
|
||||||
tty.msg('{0} Successfully installed {1}'.format(pre, pkg_id),
|
python module space set up by build_environment.start_build_process().
|
||||||
'Fetch: {0}. Build: {1}. Total: {2}.'
|
|
||||||
.format(_hms(pkg._fetch_time), _hms(build_time),
|
|
||||||
_hms(timer.total)))
|
|
||||||
_print_installed_pkg(pkg.prefix)
|
|
||||||
|
|
||||||
# Send final status that install is successful
|
This essentially wraps an instance of ``BuildProcessInstaller`` so that we can
|
||||||
spack.hooks.on_install_success(pkg.spec)
|
more easily create one in a subprocess.
|
||||||
|
|
||||||
# preserve verbosity across runs
|
This function's return value is returned to the parent process.
|
||||||
return echo
|
|
||||||
|
Arguments:
|
||||||
|
pkg (spack.package.PackageBase): the package being installed.
|
||||||
|
install_args (dict): arguments to do_install() from parent process.
|
||||||
|
|
||||||
|
"""
|
||||||
|
installer = BuildProcessInstaller(pkg, install_args)
|
||||||
|
return installer.run()
|
||||||
|
|
||||||
|
|
||||||
class BuildTask(object):
|
class BuildTask(object):
|
||||||
|
|
Loading…
Reference in a new issue