Skip to content

Commit 50d99c3

Browse files
committed
[feature] Copy build log and artifacts to a permanent location after failures
The files can be build in some selected build path (--buildpath), and the logs of successful compilation are then concentrated to some other location for permanent storage (--logfile-format). Logs of failed builds remain in the build path location so that they can be inspected. However, this setup is problematic when building software in HPC jobs. Quite often in HPC systems the build path is set to some fast storage local to the node, like NVME raid mounted on `/tmp` or `/dev/shm` (as suggested in the documentation: https://docs.easybuild.io/configuration/#buildpath). The node storage is often wiped out after the end of a job, so the log files and the artifacts are no longer available after the termination of the job. This commit adds an option (--errorlogpath)to accumulate errors in some more permanent location, so that the can be easily inspected after a failed build.
1 parent abee26d commit 50d99c3

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

easybuild/framework/easyblock.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@
7373
from easybuild.tools.config import CHECKSUM_PRIORITY_JSON, DEFAULT_ENVVAR_USERS_MODULES
7474
from easybuild.tools.config import FORCE_DOWNLOAD_ALL, FORCE_DOWNLOAD_PATCHES, FORCE_DOWNLOAD_SOURCES
7575
from easybuild.tools.config import build_option, build_path, get_log_filename, get_repository, get_repositorypath
76-
from easybuild.tools.config import install_path, log_path, package_path, source_paths
76+
from easybuild.tools.config import install_path, log_path, package_path, source_paths, error_log_path
7777
from easybuild.tools.environment import restore_env, sanitize_env
7878
from easybuild.tools.filetools import CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256
7979
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, check_lock
80-
from easybuild.tools.filetools import compute_checksum, convert_name, copy_file, create_lock, create_patch_info
80+
from easybuild.tools.filetools import convert_name, copy_file, copy_dir, create_lock, create_patch_info, is_readable
8181
from easybuild.tools.filetools import derive_alt_pypi_url, diff_files, dir_contains_files, download_file
82-
from easybuild.tools.filetools import encode_class_name, extract_file
82+
from easybuild.tools.filetools import encode_class_name, extract_file, compute_checksum
8383
from easybuild.tools.filetools import find_backup_name_candidate, get_source_tarball_from_git, is_alt_pypi_url
8484
from easybuild.tools.filetools import is_binary, is_sha256_checksum, mkdir, move_file, move_logs, read_file, remove_dir
8585
from easybuild.tools.filetools import remove_file, remove_lock, verify_checksum, weld_paths, write_file, symlink
@@ -4445,6 +4445,28 @@ def ensure_writable_log_dir(log_dir):
44454445
# there may be multiple log files, or the file name may be different due to zipping
44464446
logs = glob.glob('%s*' % application_log)
44474447
print_msg("Results of the build can be found in the log file(s) %s" % ', '.join(logs), log=_log, silent=silent)
4448+
err_log_path = error_log_path(ec=ecdict['ec'])
4449+
if err_log_path and not(success):
4450+
print_msg("Build log and artifacts copied to permanent storage: %s" % err_log_path, log=_log, silent=silent)
4451+
for log_file in logs:
4452+
target_file = os.path.join(err_log_path, os.path.basename(log_file))
4453+
copy_file(log_file, target_file)
4454+
4455+
name = ecdict['ec'].name
4456+
version = ecdict['ec'].version
4457+
4458+
toolchain_dict = ecdict['ec'].toolchain.as_dict()
4459+
toolchain_components = [
4460+
toolchain_dict['name'],
4461+
toolchain_dict['version'],
4462+
toolchain_dict['versionsuffix'],
4463+
]
4464+
toolchain_components = [s for s in toolchain_components if len(s) > 0]
4465+
toolchain = '-'.join(toolchain_components)
4466+
4467+
dest_build_path = os.path.join(err_log_path, name, version, toolchain)
4468+
if is_readable(app.builddir):
4469+
copy_dir(app.builddir, dest_build_path)
44484470

44494471
del app
44504472

easybuild/tools/config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
DEFAULT_PATH_SUBDIRS = {
107107
'buildpath': 'build',
108108
'containerpath': 'containers',
109+
'errorlogpath': 'error_log',
109110
'installpath': '',
110111
'packagepath': 'packages',
111112
'repositorypath': 'ebfiles_repo',
@@ -471,6 +472,7 @@ class ConfigurationVariables(BaseConfigurationVariables):
471472
'buildpath',
472473
'config',
473474
'containerpath',
475+
'errorlogpath',
474476
'installpath',
475477
'installpath_modules',
476478
'installpath_software',
@@ -836,6 +838,25 @@ def log_path(ec=None):
836838
return log_file_format(return_directory=True, ec=ec, date=date, timestamp=timestamp)
837839

838840

841+
def error_log_path(ec=None):
842+
"""
843+
Return the default error log path
844+
845+
This is a path where file from the build_log_path can be stored permanently
846+
:param ec: dict-like value that provides values for %(name)s and %(version)s template values
847+
"""
848+
error_log_path = ConfigurationVariables()['errorlogpath']
849+
850+
if ec is None:
851+
ec = {}
852+
853+
name, version = ec.get('name', '%(name)s'), ec.get('version', '%(version)s')
854+
date = time.strftime("%Y%m%d")
855+
timestamp = time.strftime("%H%M%S")
856+
857+
return '/'.join([error_log_path, name + '-' + version, date + '-' + timestamp])
858+
859+
839860
def get_build_log_path():
840861
"""
841862
Return (temporary) directory for build log

easybuild/tools/options.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ def config_options(self):
562562
'envvars-user-modules': ("List of environment variables that hold the base paths for which user-specific "
563563
"modules will be installed relative to", 'strlist', 'store',
564564
[DEFAULT_ENVVAR_USERS_MODULES]),
565+
'errorlogpath': ("Location where logs and artifacts are copied in case of an error",
566+
None, 'store', mk_full_default_path('errorlogpath')),
565567
'external-modules-metadata': ("List of (glob patterns for) paths to files specifying metadata "
566568
"for external modules (INI format)", 'strlist', 'store', None),
567569
'hooks': ("Location of Python module with hook implementations", 'str', 'store', None),
@@ -1137,7 +1139,7 @@ def _postprocess_config(self):
11371139
# - the <path> could also specify the location of a *remote* (Git( repository,
11381140
# which can be done in variety of formats (git@<url>:<org>/<repo>), https://<url>, etc.)
11391141
# (see also https://github.com/easybuilders/easybuild-framework/issues/3892);
1140-
path_opt_names = ['buildpath', 'containerpath', 'git_working_dirs_path', 'installpath',
1142+
path_opt_names = ['buildpath', 'containerpath', 'errorlogpath', 'git_working_dirs_path', 'installpath',
11411143
'installpath_modules', 'installpath_software', 'prefix', 'packagepath',
11421144
'robot_paths', 'sourcepath']
11431145

@@ -1147,8 +1149,8 @@ def _postprocess_config(self):
11471149
if self.options.prefix is not None:
11481150
# prefix applies to all paths, and repository has to be reinitialised to take new repositorypath in account
11491151
# in the legacy-style configuration, repository is initialised in configuration file itself
1150-
path_opts = ['buildpath', 'containerpath', 'installpath', 'packagepath', 'repository', 'repositorypath',
1151-
'sourcepath']
1152+
path_opts = ['buildpath', 'containerpath', 'errorlogpath', 'installpath', 'packagepath', 'repository',
1153+
'repositorypath', 'sourcepath']
11521154
for dest in path_opts:
11531155
if not self.options._action_taken.get(dest, False):
11541156
if dest == 'repository':

0 commit comments

Comments
 (0)