blob: a2b19d3e8f00b386ef08fbf043f9b87f1231d2c2 [file] [log] [blame]
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utility to tar Autotest and Tast artifacts from a given path, to a given path."""
import fnmatch
import os
from . import cmd_util
COMP_NONE = 0
COMP_GZIP = 1
COMP_BZIP2 = 2
COMP_XZ = 3
def _FindSourceRoot():
"""Try and find the root check out of the chromiumos tree"""
source_root = path = os.path.realpath(os.path.join(
os.path.abspath(__file__), '..', '..', '..'))
while True:
if os.path.isdir(os.path.join(path, '.repo')):
return path
elif path == '/':
break
path = os.path.dirname(path)
return source_root
SOURCE_ROOT = _FindSourceRoot()
def FromChrootPath(chroot, file):
return os.path.join(chroot, file)
def FromSysrootPath(sysroot, file):
return os.path.join(sysroot, file)
def FindFilesMatching(pattern, target = './', cwd = os.curdir,
exclude_dirs=[]):
"""Search the root directory recursively for matching filenames.
The |target| and |cwd| args allow manipulating how the found paths are
returned as well as specifying where the search needs to be executed.
If our filesystem only has /path/to/example.txt, and our pattern is '*.txt':
|target|='./', |cwd|='/path' => ./to/example.txt
|target|='to', |cwd|='/path' => to/example.txt
|target|='./', |cwd|='/path/to' => ./example.txt
|target|='/path' => /path/to/example.txt
|target|='/path/to' => /path/to/example.txt
Args:
pattern: the pattern used to match the filenames.
target: the target directory to search.
cwd: current working directory.
exclude_dirs: Directories to not include when searching.
Returns:
A list of paths of the matched files.
"""
assert cwd
assert os.path.exists(cwd)
# Backup the current working directory before changing it.
old_cwd = os.getcwd()
os.chdir(cwd)
matches = []
for directory, _, filenames in os.walk(target):
if any(directory.startswith(e) for e in exclude_dirs):
# Skip files in excluded directories.
continue
for filename in fnmatch.filter(filenames, pattern):
matches.append(os.path.join(directory, filename))
# Restore the working directory.
os.chdir(old_cwd)
return matches
class AutotestTarballBuilder(object):
"""Builds autotest tarballs for testing."""
# Archive file names.
_SERVER_PACKAGE_ARCHIVE = 'autotest_server_package.tar.bz2'
# Directory within _SERVER_PACKAGE_ARCHIVE where Tast files needed to run
# with Server-Side Packaging are stored.
_TAST_SUBDIR = 'tast'
# Tast files and directories to include in AUTOTEST_SERVER_PACKAGE relative
# to the build root.
_TAST_FILES = [
'usr/bin/tast', # Main Tast executable.
'usr/bin/remote_test_runner', # Runs remote tests.
'usr/libexec/tast/bundles', # Dir containing test bundles.
'usr/share/tast/data', # Dir containing test data.
'etc/tast/vars', # Secret variables.
]
# Tast files and directories stored in the source code.
_TAST_SOURCE_FILES = [
'src/platform/tast/tools/run_tast.sh', # Helper to run tast.
]
def __init__(self, archive_basedir, output_directory, chroot_path):
"""Init function.
Args:
archive_basedir (str): The base directory from which the archives
will be created. This path should contain the `autotest`
directory.
output_directory (str): The directory where the archives will be
written.
"""
self.archive_basedir = archive_basedir
self.output_directory = output_directory
self.chroot_path = chroot_path
def BuildFullAutotestandTastTarball(self):
"""Tar all needed Autotest & Tast files required by Docker Container.
NOTE: This does not include autotest/packages, but will include all
tests and client/deps.
Returns:
str|None - The path of the autotest server package tarball if
created.
"""
tast_files, transforms = self._GetTastServerFilesAndTarTransforms()
autotest_files = FindFilesMatching(
'*', target='autotest', cwd=self.archive_basedir,
exclude_dirs=('autotest/packages',))
tarball = os.path.join(self.output_directory,
self._SERVER_PACKAGE_ARCHIVE)
if self._BuildTarball(autotest_files + tast_files, tarball,
extra_args=transforms):
return tarball
else:
return None
def _BuildTarball(self, input_list, tarball_path, extra_args=None):
"""Tar and zip files and directories from input_list to tarball_path.
Args:
input_list: A list of files and directories to be archived.
tarball_path: Path of output tar archive file.
compressed: Whether or not the tarball should be compressed with
pbzip2.
**kwargs: Keyword arguments to pass to CreateTarball.
Returns:
Return value of CreateTarball.
"""
for pathname in input_list:
if os.path.exists(os.path.join(self.archive_basedir, pathname)):
break
else:
# If any of them exist we can create an archive, but if none
# do then we need to stop. For now, since we either pass in a
# handful of directories we don't necessarily check, or actually
# search the filesystem for lots of files, this is far more
# efficient than building out a list of files that do exist.
return None
compressor = COMP_BZIP2
chroot = self.chroot_path
return cmd_util.CreateTarball(
tarball_path, self.archive_basedir, compression=compressor,
chroot=chroot, inputs=input_list, extra_args=extra_args)
def _GetTastServerFilesAndTarTransforms(self):
"""Return Tast server files and corresponding tar transform flags.
The returned paths should be included in AUTOTEST_SERVER_PACKAGE. The
--transform arguments should be passed to GNU tar to convert the paths
to appropriate destinations in the tarball.
Returns:
(files, transforms), where files is a list of absolute paths to
Tast server files/directories and transforms is a list of
--transform arguments to pass to GNU tar when archiving those
files.
"""
files = []
transforms = []
for path in self._GetTastFiles():
if not os.path.exists(path):
continue
files.append(path)
dest = os.path.join(self._TAST_SUBDIR, os.path.basename(path))
transforms.append('--transform=s|^%s|%s|' %
(os.path.relpath(path, '/'), dest))
return files, transforms
def _GetTastFiles(self):
"""Build out the paths to the tast files.
Returns:
list[str] - The paths to the files.
"""
files = []
files.extend(FromChrootPath(self.chroot_path, x)
for x in self._TAST_FILES)
for filename in self._TAST_SOURCE_FILES:
files.append(os.path.join(SOURCE_ROOT, filename))
return files