ci: copy logs from failed job stage dir (#49884)

This commit is contained in:
Ryan Krattiger 2025-04-22 16:23:49 -05:00 committed by GitHub
parent c62cc6a45d
commit 3fd6066e54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 91 additions and 28 deletions

View File

@ -764,7 +764,7 @@ def copy_tree(
files = glob.glob(src) files = glob.glob(src)
if not files: if not files:
raise OSError("No such file or directory: '{0}'".format(src)) raise OSError("No such file or directory: '{0}'".format(src), errno.ENOENT)
# For Windows hard-links and junctions, the source path must exist to make a symlink. Add # For Windows hard-links and junctions, the source path must exist to make a symlink. Add
# all symlinks to this list while traversing the tree, then when finished, make all # all symlinks to this list while traversing the tree, then when finished, make all

View File

@ -24,6 +24,7 @@
import spack import spack
import spack.binary_distribution as bindist import spack.binary_distribution as bindist
import spack.builder
import spack.config as cfg import spack.config as cfg
import spack.environment as ev import spack.environment as ev
import spack.error import spack.error
@ -613,32 +614,40 @@ def copy_stage_logs_to_artifacts(job_spec: spack.spec.Spec, job_log_dir: str) ->
job_spec, and attempts to copy the files into the directory given job_spec, and attempts to copy the files into the directory given
by job_log_dir. by job_log_dir.
Args: Parameters:
job_spec: spec associated with spack install log job_spec: spec associated with spack install log
job_log_dir: path into which build log should be copied job_log_dir: path into which build log should be copied
""" """
tty.debug(f"job spec: {job_spec}") tty.debug(f"job spec: {job_spec}")
if not job_spec.concrete:
try: tty.warn("Cannot copy artifacts for non-concrete specs")
package_metadata_root = pathlib.Path(spack.store.STORE.layout.metadata_path(job_spec))
except spack.error.SpackError as e:
tty.error(f"Cannot copy logs: {str(e)}")
return return
# Get the package's archived files package_metadata_root = pathlib.Path(spack.store.STORE.layout.metadata_path(job_spec))
archive_files = [] if not os.path.isdir(package_metadata_root):
archive_root = package_metadata_root / "archived-files" # Fallback to using the stage directory
if archive_root.is_dir(): job_pkg = job_spec.package
archive_files = [f for f in archive_root.rglob("*") if f.is_file()]
else:
msg = "Cannot copy package archived files: archived-files must be a directory"
tty.warn(msg)
package_metadata_root = pathlib.Path(job_pkg.stage.path)
archive_files = spack.builder.create(job_pkg).archive_files
tty.warn("Package not installed, falling back to use stage dir")
tty.debug(f"stage dir: {package_metadata_root}")
else:
# Get the package's archived files
archive_files = []
archive_root = package_metadata_root / "archived-files"
if os.path.isdir(archive_root):
archive_files = [str(f) for f in archive_root.rglob("*") if os.path.isfile(f)]
else:
tty.debug(f"No archived files detected at {archive_root}")
# Try zipped and unzipped versions of the build log
build_log_zipped = package_metadata_root / "spack-build-out.txt.gz" build_log_zipped = package_metadata_root / "spack-build-out.txt.gz"
build_log = package_metadata_root / "spack-build-out.txt"
build_env_mods = package_metadata_root / "spack-build-env.txt" build_env_mods = package_metadata_root / "spack-build-env.txt"
for f in [build_log_zipped, build_env_mods, *archive_files]: for f in [build_log_zipped, build_log, build_env_mods, *archive_files]:
copy_files_to_artifacts(str(f), job_log_dir) copy_files_to_artifacts(str(f), job_log_dir, compress_artifacts=True)
def copy_test_logs_to_artifacts(test_stage, job_test_dir): def copy_test_logs_to_artifacts(test_stage, job_test_dir):
@ -651,11 +660,12 @@ def copy_test_logs_to_artifacts(test_stage, job_test_dir):
""" """
tty.debug(f"test stage: {test_stage}") tty.debug(f"test stage: {test_stage}")
if not os.path.exists(test_stage): if not os.path.exists(test_stage):
msg = f"Cannot copy test logs: job test stage ({test_stage}) does not exist" tty.error(f"Cannot copy test logs: job test stage ({test_stage}) does not exist")
tty.error(msg)
return return
copy_files_to_artifacts(os.path.join(test_stage, "*", "*.txt"), job_test_dir) copy_files_to_artifacts(
os.path.join(test_stage, "*", "*.txt"), job_test_dir, compress_artifacts=True
)
def download_and_extract_artifacts(url, work_dir) -> str: def download_and_extract_artifacts(url, work_dir) -> str:

View File

@ -2,9 +2,13 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import copy import copy
import errno
import glob
import gzip
import json import json
import os import os
import re import re
import shutil
import sys import sys
import time import time
from collections import deque from collections import deque
@ -25,6 +29,7 @@
import spack.mirrors.mirror import spack.mirrors.mirror
import spack.schema import spack.schema
import spack.spec import spack.spec
import spack.util.compression as compression
import spack.util.spack_yaml as syaml import spack.util.spack_yaml as syaml
import spack.util.url as url_util import spack.util.url as url_util
import spack.util.web as web_util import spack.util.web as web_util
@ -40,22 +45,67 @@
_urlopen = web_util.urlopen _urlopen = web_util.urlopen
def copy_files_to_artifacts(src, artifacts_dir): def copy_gzipped(glob_or_path: str, dest: str) -> None:
"""Copy all of the files in the source glob/path to the destination.
Args:
glob_or_path: path to file to test
dest: destination path to copy to
"""
files = glob.glob(glob_or_path)
if not files:
raise OSError("No such file or directory: '{0}'".format(glob_or_path), errno.ENOENT)
if len(files) > 1 and not os.path.isdir(dest):
raise ValueError(
"'{0}' matches multiple files but '{1}' is not a directory".format(glob_or_path, dest)
)
def is_gzipped(path):
with open(path, "rb") as fd:
return compression.GZipFileType().matches_magic(fd)
for src in files:
if is_gzipped(src):
fs.copy(src, dest)
else:
# Compress and copy in one step
src_name = os.path.basename(src)
if os.path.isdir(dest):
zipped = os.path.join(dest, f"{src_name}.gz")
elif not dest.endswith(".gz"):
zipped = f"{dest}.gz"
else:
zipped = dest
with open(src, "rb") as fin, gzip.open(zipped, "wb") as fout:
shutil.copyfileobj(fin, fout)
def copy_files_to_artifacts(
src: str, artifacts_dir: str, *, compress_artifacts: bool = False
) -> None:
""" """
Copy file(s) to the given artifacts directory Copy file(s) to the given artifacts directory
Parameters: Args:
src (str): the glob-friendly path expression for the file(s) to copy src (str): the glob-friendly path expression for the file(s) to copy
artifacts_dir (str): the destination directory artifacts_dir (str): the destination directory
compress_artifacts (bool): option to compress copied artifacts using Gzip
""" """
try: try:
fs.copy(src, artifacts_dir)
if compress_artifacts:
copy_gzipped(src, artifacts_dir)
else:
fs.copy(src, artifacts_dir)
except Exception as err: except Exception as err:
msg = ( tty.warn(
f"Unable to copy files ({src}) to artifacts {artifacts_dir} due to " (
f"exception: {str(err)}" f"Unable to copy files ({src}) to artifacts {artifacts_dir} due to "
f"exception: {str(err)}"
)
) )
tty.warn(msg)
def win_quote(quote_str: str) -> str: def win_quote(quote_str: str) -> str:

View File

@ -453,7 +453,7 @@ def ci_rebuild(args):
# Arguments when installing the root from sources # Arguments when installing the root from sources
deps_install_args = install_args + ["--only=dependencies"] deps_install_args = install_args + ["--only=dependencies"]
root_install_args = install_args + ["--only=package"] root_install_args = install_args + ["--keep-stage", "--only=package"]
if cdash_handler: if cdash_handler:
# Add additional arguments to `spack install` for CDash reporting. # Add additional arguments to `spack install` for CDash reporting.
@ -493,6 +493,9 @@ def ci_rebuild(args):
# Copy logs and archived files from the install metadata (.spack) directory to artifacts now # Copy logs and archived files from the install metadata (.spack) directory to artifacts now
spack_ci.copy_stage_logs_to_artifacts(job_spec, job_log_dir) spack_ci.copy_stage_logs_to_artifacts(job_spec, job_log_dir)
# Clear the stage directory
spack.stage.purge()
# If the installation succeeded and we're running stand-alone tests for # If the installation succeeded and we're running stand-alone tests for
# the package, run them and copy the output. Failures of any kind should # the package, run them and copy the output. Failures of any kind should
# *not* terminate the build process or preclude creating the build cache. # *not* terminate the build process or preclude creating the build cache.