buildcache push: improve printing (#36141)
This commit is contained in:
parent
11aa2d721e
commit
c7e60f441a
@ -24,7 +24,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
from contextlib import closing, contextmanager
|
from contextlib import closing, contextmanager
|
||||||
from gzip import GzipFile
|
from gzip import GzipFile
|
||||||
from typing import Union
|
from typing import List, NamedTuple, Optional, Union
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
|
|
||||||
import ruamel.yaml as yaml
|
import ruamel.yaml as yaml
|
||||||
@ -509,13 +509,11 @@ def _binary_index():
|
|||||||
|
|
||||||
|
|
||||||
class NoOverwriteException(spack.error.SpackError):
|
class NoOverwriteException(spack.error.SpackError):
|
||||||
"""
|
"""Raised when a file would be overwritten"""
|
||||||
Raised when a file exists and must be overwritten.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, file_path):
|
def __init__(self, file_path):
|
||||||
super(NoOverwriteException, self).__init__(
|
super(NoOverwriteException, self).__init__(
|
||||||
'"{}" exists in buildcache. Use --force flag to overwrite.'.format(file_path)
|
f"Refusing to overwrite the following file: {file_path}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1205,48 +1203,42 @@ def _do_create_tarball(tarfile_path, binaries_dir, pkg_dir, buildinfo):
|
|||||||
tar_add_metadata(tar, buildinfo_file_name(pkg_dir), buildinfo)
|
tar_add_metadata(tar, buildinfo_file_name(pkg_dir), buildinfo)
|
||||||
|
|
||||||
|
|
||||||
def _build_tarball(
|
class PushOptions(NamedTuple):
|
||||||
spec,
|
#: Overwrite existing tarball/metadata files in buildcache
|
||||||
out_url,
|
force: bool = False
|
||||||
force=False,
|
|
||||||
relative=False,
|
#: Whether to use relative RPATHs
|
||||||
unsigned=False,
|
relative: bool = False
|
||||||
allow_root=False,
|
|
||||||
key=None,
|
#: Allow absolute paths to package prefixes when creating a tarball
|
||||||
regenerate_index=False,
|
allow_root: bool = False
|
||||||
):
|
|
||||||
|
#: Regenerated indices after pushing
|
||||||
|
regenerate_index: bool = False
|
||||||
|
|
||||||
|
#: Whether to sign or not.
|
||||||
|
unsigned: bool = False
|
||||||
|
|
||||||
|
#: What key to use for signing
|
||||||
|
key: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def push_or_raise(spec: Spec, out_url: str, options: PushOptions):
|
||||||
"""
|
"""
|
||||||
Build a tarball from given spec and put it into the directory structure
|
Build a tarball from given spec and put it into the directory structure
|
||||||
used at the mirror (following <tarball_directory_name>).
|
used at the mirror (following <tarball_directory_name>).
|
||||||
|
|
||||||
|
This method raises :py:class:`NoOverwriteException` when ``force=False`` and the tarball or
|
||||||
|
spec.json file already exist in the buildcache.
|
||||||
"""
|
"""
|
||||||
if not spec.concrete:
|
if not spec.concrete:
|
||||||
raise ValueError("spec must be concrete to build tarball")
|
raise ValueError("spec must be concrete to build tarball")
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(dir=spack.stage.get_stage_root()) as tmpdir:
|
with tempfile.TemporaryDirectory(dir=spack.stage.get_stage_root()) as tmpdir:
|
||||||
_build_tarball_in_stage_dir(
|
_build_tarball_in_stage_dir(spec, out_url, stage_dir=tmpdir, options=options)
|
||||||
spec,
|
|
||||||
out_url,
|
|
||||||
stage_dir=tmpdir,
|
|
||||||
force=force,
|
|
||||||
relative=relative,
|
|
||||||
unsigned=unsigned,
|
|
||||||
allow_root=allow_root,
|
|
||||||
key=key,
|
|
||||||
regenerate_index=regenerate_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_tarball_in_stage_dir(
|
def _build_tarball_in_stage_dir(spec: Spec, out_url: str, stage_dir: str, options: PushOptions):
|
||||||
spec,
|
|
||||||
out_url,
|
|
||||||
stage_dir,
|
|
||||||
force=False,
|
|
||||||
relative=False,
|
|
||||||
unsigned=False,
|
|
||||||
allow_root=False,
|
|
||||||
key=None,
|
|
||||||
regenerate_index=False,
|
|
||||||
):
|
|
||||||
cache_prefix = build_cache_prefix(stage_dir)
|
cache_prefix = build_cache_prefix(stage_dir)
|
||||||
tarfile_name = tarball_name(spec, ".spack")
|
tarfile_name = tarball_name(spec, ".spack")
|
||||||
tarfile_dir = os.path.join(cache_prefix, tarball_directory_name(spec))
|
tarfile_dir = os.path.join(cache_prefix, tarball_directory_name(spec))
|
||||||
@ -1256,7 +1248,7 @@ def _build_tarball_in_stage_dir(
|
|||||||
|
|
||||||
mkdirp(tarfile_dir)
|
mkdirp(tarfile_dir)
|
||||||
if web_util.url_exists(remote_spackfile_path):
|
if web_util.url_exists(remote_spackfile_path):
|
||||||
if force:
|
if options.force:
|
||||||
web_util.remove_url(remote_spackfile_path)
|
web_util.remove_url(remote_spackfile_path)
|
||||||
else:
|
else:
|
||||||
raise NoOverwriteException(url_util.format(remote_spackfile_path))
|
raise NoOverwriteException(url_util.format(remote_spackfile_path))
|
||||||
@ -1276,7 +1268,7 @@ def _build_tarball_in_stage_dir(
|
|||||||
remote_signed_specfile_path = "{0}.sig".format(remote_specfile_path)
|
remote_signed_specfile_path = "{0}.sig".format(remote_specfile_path)
|
||||||
|
|
||||||
# If force and exists, overwrite. Otherwise raise exception on collision.
|
# If force and exists, overwrite. Otherwise raise exception on collision.
|
||||||
if force:
|
if options.force:
|
||||||
if web_util.url_exists(remote_specfile_path):
|
if web_util.url_exists(remote_specfile_path):
|
||||||
web_util.remove_url(remote_specfile_path)
|
web_util.remove_url(remote_specfile_path)
|
||||||
if web_util.url_exists(remote_signed_specfile_path):
|
if web_util.url_exists(remote_signed_specfile_path):
|
||||||
@ -1293,7 +1285,7 @@ def _build_tarball_in_stage_dir(
|
|||||||
# mode, Spack unfortunately *does* mutate rpaths and links ahead of time.
|
# mode, Spack unfortunately *does* mutate rpaths and links ahead of time.
|
||||||
# For now, we only make a full copy of the spec prefix when in relative mode.
|
# For now, we only make a full copy of the spec prefix when in relative mode.
|
||||||
|
|
||||||
if relative:
|
if options.relative:
|
||||||
# tarfile is used because it preserves hardlink etc best.
|
# tarfile is used because it preserves hardlink etc best.
|
||||||
binaries_dir = workdir
|
binaries_dir = workdir
|
||||||
temp_tarfile_name = tarball_name(spec, ".tar")
|
temp_tarfile_name = tarball_name(spec, ".tar")
|
||||||
@ -1307,19 +1299,19 @@ def _build_tarball_in_stage_dir(
|
|||||||
binaries_dir = spec.prefix
|
binaries_dir = spec.prefix
|
||||||
|
|
||||||
# create info for later relocation and create tar
|
# create info for later relocation and create tar
|
||||||
buildinfo = get_buildinfo_dict(spec, relative)
|
buildinfo = get_buildinfo_dict(spec, options.relative)
|
||||||
|
|
||||||
# optionally make the paths in the binaries relative to each other
|
# optionally make the paths in the binaries relative to each other
|
||||||
# in the spack install tree before creating tarball
|
# in the spack install tree before creating tarball
|
||||||
if relative:
|
if options.relative:
|
||||||
make_package_relative(workdir, spec, buildinfo, allow_root)
|
make_package_relative(workdir, spec, buildinfo, options.allow_root)
|
||||||
elif not allow_root:
|
elif not options.allow_root:
|
||||||
ensure_package_relocatable(buildinfo, binaries_dir)
|
ensure_package_relocatable(buildinfo, binaries_dir)
|
||||||
|
|
||||||
_do_create_tarball(tarfile_path, binaries_dir, pkg_dir, buildinfo)
|
_do_create_tarball(tarfile_path, binaries_dir, pkg_dir, buildinfo)
|
||||||
|
|
||||||
# remove copy of install directory
|
# remove copy of install directory
|
||||||
if relative:
|
if options.relative:
|
||||||
shutil.rmtree(workdir)
|
shutil.rmtree(workdir)
|
||||||
|
|
||||||
# get the sha256 checksum of the tarball
|
# get the sha256 checksum of the tarball
|
||||||
@ -1342,7 +1334,7 @@ def _build_tarball_in_stage_dir(
|
|||||||
# This will be used to determine is the directory layout has changed.
|
# This will be used to determine is the directory layout has changed.
|
||||||
buildinfo = {}
|
buildinfo = {}
|
||||||
buildinfo["relative_prefix"] = os.path.relpath(spec.prefix, spack.store.layout.root)
|
buildinfo["relative_prefix"] = os.path.relpath(spec.prefix, spack.store.layout.root)
|
||||||
buildinfo["relative_rpaths"] = relative
|
buildinfo["relative_rpaths"] = options.relative
|
||||||
spec_dict["buildinfo"] = buildinfo
|
spec_dict["buildinfo"] = buildinfo
|
||||||
|
|
||||||
with open(specfile_path, "w") as outfile:
|
with open(specfile_path, "w") as outfile:
|
||||||
@ -1353,40 +1345,40 @@ def _build_tarball_in_stage_dir(
|
|||||||
json.dump(spec_dict, outfile, indent=0, separators=(",", ":"))
|
json.dump(spec_dict, outfile, indent=0, separators=(",", ":"))
|
||||||
|
|
||||||
# sign the tarball and spec file with gpg
|
# sign the tarball and spec file with gpg
|
||||||
if not unsigned:
|
if not options.unsigned:
|
||||||
key = select_signing_key(key)
|
key = select_signing_key(options.key)
|
||||||
sign_specfile(key, force, specfile_path)
|
sign_specfile(key, options.force, specfile_path)
|
||||||
|
|
||||||
# push tarball and signed spec json to remote mirror
|
# push tarball and signed spec json to remote mirror
|
||||||
web_util.push_to_url(spackfile_path, remote_spackfile_path, keep_original=False)
|
web_util.push_to_url(spackfile_path, remote_spackfile_path, keep_original=False)
|
||||||
web_util.push_to_url(
|
web_util.push_to_url(
|
||||||
signed_specfile_path if not unsigned else specfile_path,
|
signed_specfile_path if not options.unsigned else specfile_path,
|
||||||
remote_signed_specfile_path if not unsigned else remote_specfile_path,
|
remote_signed_specfile_path if not options.unsigned else remote_specfile_path,
|
||||||
keep_original=False,
|
keep_original=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
tty.debug('Buildcache for "{0}" written to \n {1}'.format(spec, remote_spackfile_path))
|
|
||||||
|
|
||||||
# push the key to the build cache's _pgp directory so it can be
|
# push the key to the build cache's _pgp directory so it can be
|
||||||
# imported
|
# imported
|
||||||
if not unsigned:
|
if not options.unsigned:
|
||||||
push_keys(out_url, keys=[key], regenerate_index=regenerate_index, tmpdir=stage_dir)
|
push_keys(out_url, keys=[key], regenerate_index=options.regenerate_index, tmpdir=stage_dir)
|
||||||
|
|
||||||
# create an index.json for the build_cache directory so specs can be
|
# create an index.json for the build_cache directory so specs can be
|
||||||
# found
|
# found
|
||||||
if regenerate_index:
|
if options.regenerate_index:
|
||||||
generate_package_index(url_util.join(out_url, os.path.relpath(cache_prefix, stage_dir)))
|
generate_package_index(url_util.join(out_url, os.path.relpath(cache_prefix, stage_dir)))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def nodes_to_be_packaged(specs, root=True, dependencies=True):
|
def specs_to_be_packaged(
|
||||||
|
specs: List[Spec], root: bool = True, dependencies: bool = True
|
||||||
|
) -> List[Spec]:
|
||||||
"""Return the list of nodes to be packaged, given a list of specs.
|
"""Return the list of nodes to be packaged, given a list of specs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
specs (List[spack.spec.Spec]): list of root specs to be processed
|
specs: list of root specs to be processed
|
||||||
root (bool): include the root of each spec in the nodes
|
root: include the root of each spec in the nodes
|
||||||
dependencies (bool): include the dependencies of each
|
dependencies: include the dependencies of each
|
||||||
spec in the nodes
|
spec in the nodes
|
||||||
"""
|
"""
|
||||||
if not root and not dependencies:
|
if not root and not dependencies:
|
||||||
@ -1404,32 +1396,26 @@ def nodes_to_be_packaged(specs, root=True, dependencies=True):
|
|||||||
return list(filter(packageable, nodes))
|
return list(filter(packageable, nodes))
|
||||||
|
|
||||||
|
|
||||||
def push(specs, push_url, include_root: bool = True, include_dependencies: bool = True, **kwargs):
|
def push(spec: Spec, mirror_url: str, options: PushOptions):
|
||||||
"""Create a binary package for each of the specs passed as input and push them
|
"""Create and push binary package for a single spec to the specified
|
||||||
to a given push URL.
|
mirror url.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
specs (List[spack.spec.Spec]): installed specs to be packaged
|
spec: Spec to package and push
|
||||||
push_url (str): url where to push the binary package
|
mirror_url: Desired destination url for binary package
|
||||||
include_root (bool): include the root of each spec in the nodes
|
options:
|
||||||
include_dependencies (bool): include the dependencies of each
|
|
||||||
spec in the nodes
|
Returns:
|
||||||
**kwargs: TODO
|
True if package was pushed, False otherwise.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Be explicit about the arugment type
|
|
||||||
if type(include_root) != bool or type(include_dependencies) != bool:
|
|
||||||
raise ValueError("Expected include_root/include_dependencies to be True/False")
|
|
||||||
|
|
||||||
nodes = nodes_to_be_packaged(specs, root=include_root, dependencies=include_dependencies)
|
|
||||||
|
|
||||||
# TODO: This seems to be an easy target for task
|
|
||||||
# TODO: distribution using a parallel pool
|
|
||||||
for node in nodes:
|
|
||||||
try:
|
try:
|
||||||
_build_tarball(node, push_url, **kwargs)
|
push_or_raise(spec, mirror_url, options)
|
||||||
except NoOverwriteException as e:
|
except NoOverwriteException as e:
|
||||||
warnings.warn(str(e))
|
warnings.warn(str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def try_verify(specfile_path):
|
def try_verify(specfile_path):
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from collections import namedtuple
|
||||||
|
from typing import List, Optional
|
||||||
from urllib.error import HTTPError, URLError
|
from urllib.error import HTTPError, URLError
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from urllib.request import HTTPHandler, Request, build_opener
|
from urllib.request import HTTPHandler, Request, build_opener
|
||||||
@ -33,6 +35,7 @@
|
|||||||
import spack.mirror
|
import spack.mirror
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.repo
|
import spack.repo
|
||||||
|
import spack.spec
|
||||||
import spack.util.git
|
import spack.util.git
|
||||||
import spack.util.gpg as gpg_util
|
import spack.util.gpg as gpg_util
|
||||||
import spack.util.spack_yaml as syaml
|
import spack.util.spack_yaml as syaml
|
||||||
@ -52,6 +55,8 @@
|
|||||||
spack_gpg = spack.main.SpackCommand("gpg")
|
spack_gpg = spack.main.SpackCommand("gpg")
|
||||||
spack_compiler = spack.main.SpackCommand("compiler")
|
spack_compiler = spack.main.SpackCommand("compiler")
|
||||||
|
|
||||||
|
PushResult = namedtuple("PushResult", "success url")
|
||||||
|
|
||||||
|
|
||||||
class TemporaryDirectory(object):
|
class TemporaryDirectory(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -1585,33 +1590,28 @@ def configure_compilers(compiler_action, scope=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _push_mirror_contents(env, specfile_path, sign_binaries, mirror_url):
|
def _push_mirror_contents(input_spec, sign_binaries, mirror_url):
|
||||||
"""Unchecked version of the public API, for easier mocking"""
|
"""Unchecked version of the public API, for easier mocking"""
|
||||||
unsigned = not sign_binaries
|
unsigned = not sign_binaries
|
||||||
tty.debug("Creating buildcache ({0})".format("unsigned" if unsigned else "signed"))
|
tty.debug("Creating buildcache ({0})".format("unsigned" if unsigned else "signed"))
|
||||||
hashes = env.all_hashes() if env else None
|
|
||||||
matches = spack.store.specfile_matches(specfile_path, hashes=hashes)
|
|
||||||
push_url = spack.mirror.Mirror.from_url(mirror_url).push_url
|
push_url = spack.mirror.Mirror.from_url(mirror_url).push_url
|
||||||
kwargs = {"force": True, "allow_root": True, "unsigned": unsigned}
|
return bindist.push(
|
||||||
bindist.push(matches, push_url, include_root=True, include_dependencies=False, **kwargs)
|
input_spec, push_url, bindist.PushOptions(force=True, allow_root=True, unsigned=unsigned)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
def push_mirror_contents(input_spec: spack.spec.Spec, mirror_url, sign_binaries):
|
||||||
"""Push one or more binary packages to the mirror.
|
"""Push one or more binary packages to the mirror.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
env (spack.environment.Environment): Optional environment. If
|
input_spec(spack.spec.Spec): Installed spec to push
|
||||||
provided, it is used to make sure binary package to push
|
|
||||||
exists in the environment.
|
|
||||||
specfile_path (str): Path to spec.json corresponding to built pkg
|
|
||||||
to push.
|
|
||||||
mirror_url (str): Base url of target mirror
|
mirror_url (str): Base url of target mirror
|
||||||
sign_binaries (bool): If True, spack will attempt to sign binary
|
sign_binaries (bool): If True, spack will attempt to sign binary
|
||||||
package before pushing.
|
package before pushing.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
_push_mirror_contents(env, specfile_path, sign_binaries, mirror_url)
|
return _push_mirror_contents(input_spec, sign_binaries, mirror_url)
|
||||||
except Exception as inst:
|
except Exception as inst:
|
||||||
# If the mirror we're pushing to is on S3 and there's some
|
# If the mirror we're pushing to is on S3 and there's some
|
||||||
# permissions problem, for example, we can't just target
|
# permissions problem, for example, we can't just target
|
||||||
@ -1628,6 +1628,7 @@ def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
|||||||
if any(x in err_msg for x in ["Access Denied", "InvalidAccessKeyId"]):
|
if any(x in err_msg for x in ["Access Denied", "InvalidAccessKeyId"]):
|
||||||
tty.msg("Permission problem writing to {0}".format(mirror_url))
|
tty.msg("Permission problem writing to {0}".format(mirror_url))
|
||||||
tty.msg(err_msg)
|
tty.msg(err_msg)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
raise inst
|
raise inst
|
||||||
|
|
||||||
@ -2131,39 +2132,50 @@ def process_command(name, commands, repro_dir):
|
|||||||
return exit_code
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
def create_buildcache(**kwargs):
|
def create_buildcache(
|
||||||
|
input_spec: spack.spec.Spec,
|
||||||
|
*,
|
||||||
|
pr_pipeline: bool,
|
||||||
|
pipeline_mirror_url: Optional[str] = None,
|
||||||
|
buildcache_mirror_url: Optional[str] = None,
|
||||||
|
) -> List[PushResult]:
|
||||||
"""Create the buildcache at the provided mirror(s).
|
"""Create the buildcache at the provided mirror(s).
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
kwargs (dict): dictionary of arguments used to create the buildcache
|
input_spec: Installed spec to package and push
|
||||||
|
buildcache_mirror_url: URL for the buildcache mirror
|
||||||
|
pipeline_mirror_url: URL for the pipeline mirror
|
||||||
|
pr_pipeline: True if the CI job is for a PR
|
||||||
|
|
||||||
List of recognized keys:
|
Returns: A list of PushResults, indicating success or failure.
|
||||||
|
|
||||||
* "env" (spack.environment.Environment): the active environment
|
|
||||||
* "buildcache_mirror_url" (str or None): URL for the buildcache mirror
|
|
||||||
* "pipeline_mirror_url" (str or None): URL for the pipeline mirror
|
|
||||||
* "pr_pipeline" (bool): True if the CI job is for a PR
|
|
||||||
* "json_path" (str): path the the spec's JSON file
|
|
||||||
"""
|
"""
|
||||||
env = kwargs.get("env")
|
|
||||||
buildcache_mirror_url = kwargs.get("buildcache_mirror_url")
|
|
||||||
pipeline_mirror_url = kwargs.get("pipeline_mirror_url")
|
|
||||||
pr_pipeline = kwargs.get("pr_pipeline")
|
|
||||||
json_path = kwargs.get("json_path")
|
|
||||||
|
|
||||||
sign_binaries = pr_pipeline is False and can_sign_binaries()
|
sign_binaries = pr_pipeline is False and can_sign_binaries()
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
# Create buildcache in either the main remote mirror, or in the
|
# Create buildcache in either the main remote mirror, or in the
|
||||||
# per-PR mirror, if this is a PR pipeline
|
# per-PR mirror, if this is a PR pipeline
|
||||||
if buildcache_mirror_url:
|
if buildcache_mirror_url:
|
||||||
push_mirror_contents(env, json_path, buildcache_mirror_url, sign_binaries)
|
results.append(
|
||||||
|
PushResult(
|
||||||
|
success=push_mirror_contents(input_spec, buildcache_mirror_url, sign_binaries),
|
||||||
|
url=buildcache_mirror_url,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Create another copy of that buildcache in the per-pipeline
|
# Create another copy of that buildcache in the per-pipeline
|
||||||
# temporary storage mirror (this is only done if either
|
# temporary storage mirror (this is only done if either
|
||||||
# artifacts buildcache is enabled or a temporary storage url
|
# artifacts buildcache is enabled or a temporary storage url
|
||||||
# prefix is set)
|
# prefix is set)
|
||||||
if pipeline_mirror_url:
|
if pipeline_mirror_url:
|
||||||
push_mirror_contents(env, json_path, pipeline_mirror_url, sign_binaries)
|
results.append(
|
||||||
|
PushResult(
|
||||||
|
success=push_mirror_contents(input_spec, pipeline_mirror_url, sign_binaries),
|
||||||
|
url=pipeline_mirror_url,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def write_broken_spec(url, pkg_name, stack_name, job_url, pipeline_url, spec_dict):
|
def write_broken_spec(url, pkg_name, stack_name, job_url, pipeline_url, spec_dict):
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
import llnl.util.tty.color as clr
|
||||||
|
from llnl.util.lang import elide_list
|
||||||
|
|
||||||
import spack.binary_distribution as bindist
|
import spack.binary_distribution as bindist
|
||||||
import spack.cmd
|
import spack.cmd
|
||||||
@ -449,30 +451,68 @@ def create_fn(args):
|
|||||||
|
|
||||||
# TODO: remove this in 0.21. If we have mirror_flag, the first
|
# TODO: remove this in 0.21. If we have mirror_flag, the first
|
||||||
# spec is in the positional mirror arg due to argparse limitations.
|
# spec is in the positional mirror arg due to argparse limitations.
|
||||||
specs = args.specs
|
input_specs = args.specs
|
||||||
if args.mirror_flag and args.mirror:
|
if args.mirror_flag and args.mirror:
|
||||||
specs.insert(0, args.mirror)
|
input_specs.insert(0, args.mirror)
|
||||||
|
|
||||||
url = mirror.push_url
|
url = mirror.push_url
|
||||||
|
|
||||||
matches = _matching_specs(specs, args.spec_file)
|
specs = bindist.specs_to_be_packaged(
|
||||||
|
_matching_specs(input_specs, args.spec_file),
|
||||||
|
root="package" in args.things_to_install,
|
||||||
|
dependencies="dependencies" in args.things_to_install,
|
||||||
|
)
|
||||||
|
|
||||||
msg = "Pushing binary packages to {0}/build_cache".format(url)
|
# When pushing multiple specs, print the url once ahead of time, as well as how
|
||||||
tty.msg(msg)
|
# many specs are being pushed.
|
||||||
kwargs = {
|
if len(specs) > 1:
|
||||||
"key": args.key,
|
tty.info(f"Selected {len(specs)} specs to push to {url}")
|
||||||
"force": args.force,
|
|
||||||
"relative": args.rel,
|
skipped = []
|
||||||
"unsigned": args.unsigned,
|
|
||||||
"allow_root": args.allow_root,
|
# tty printing
|
||||||
"regenerate_index": args.rebuild_index,
|
color = clr.get_color_when()
|
||||||
}
|
format_spec = lambda s: s.format("{name}{@version}{/hash:7}", color=color)
|
||||||
bindist.push(
|
total_specs = len(specs)
|
||||||
matches,
|
digits = len(str(total_specs))
|
||||||
|
|
||||||
|
for i, spec in enumerate(specs):
|
||||||
|
try:
|
||||||
|
bindist.push_or_raise(
|
||||||
|
spec,
|
||||||
url,
|
url,
|
||||||
include_root="package" in args.things_to_install,
|
bindist.PushOptions(
|
||||||
include_dependencies="dependencies" in args.things_to_install,
|
force=args.force,
|
||||||
**kwargs,
|
relative=args.rel,
|
||||||
|
unsigned=args.unsigned,
|
||||||
|
allow_root=args.allow_root,
|
||||||
|
key=args.key,
|
||||||
|
regenerate_index=args.rebuild_index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if total_specs > 1:
|
||||||
|
msg = f"[{i+1:{digits}}/{total_specs}] Pushed {format_spec(spec)}"
|
||||||
|
else:
|
||||||
|
msg = f"Pushed {format_spec(spec)} to {url}"
|
||||||
|
|
||||||
|
tty.info(msg)
|
||||||
|
|
||||||
|
except bindist.NoOverwriteException:
|
||||||
|
skipped.append(format_spec(spec))
|
||||||
|
|
||||||
|
if skipped:
|
||||||
|
if len(specs) == 1:
|
||||||
|
tty.info("The spec is already in the buildcache. Use --force to overwrite it.")
|
||||||
|
elif len(skipped) == len(specs):
|
||||||
|
tty.info("All specs are already in the buildcache. Use --force to overwite them.")
|
||||||
|
else:
|
||||||
|
tty.info(
|
||||||
|
"The following {} specs were skipped as they already exist in the buildcache:\n"
|
||||||
|
" {}\n"
|
||||||
|
" Use --force to overwrite them.".format(
|
||||||
|
len(skipped), ", ".join(elide_list(skipped, 5))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
import llnl.util.tty.color as clr
|
||||||
|
|
||||||
import spack.binary_distribution as bindist
|
import spack.binary_distribution as bindist
|
||||||
import spack.ci as spack_ci
|
import spack.ci as spack_ci
|
||||||
@ -670,12 +671,19 @@ def ci_rebuild(args):
|
|||||||
# outside of the pipeline environment.
|
# outside of the pipeline environment.
|
||||||
if install_exit_code == 0:
|
if install_exit_code == 0:
|
||||||
if buildcache_mirror_url or pipeline_mirror_url:
|
if buildcache_mirror_url or pipeline_mirror_url:
|
||||||
spack_ci.create_buildcache(
|
for result in spack_ci.create_buildcache(
|
||||||
env=env,
|
input_spec=job_spec,
|
||||||
buildcache_mirror_url=buildcache_mirror_url,
|
buildcache_mirror_url=buildcache_mirror_url,
|
||||||
pipeline_mirror_url=pipeline_mirror_url,
|
pipeline_mirror_url=pipeline_mirror_url,
|
||||||
pr_pipeline=spack_is_pr_pipeline,
|
pr_pipeline=spack_is_pr_pipeline,
|
||||||
json_path=job_spec_json_path,
|
):
|
||||||
|
msg = tty.msg if result.success else tty.warn
|
||||||
|
msg(
|
||||||
|
"{} {} to {}".format(
|
||||||
|
"Pushed" if result.success else "Failed to push",
|
||||||
|
job_spec.format("{name}{@version}{/hash:7}", color=clr.get_color_when()),
|
||||||
|
result.url,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If this is a develop pipeline, check if the spec that we just built is
|
# If this is a develop pipeline, check if the spec that we just built is
|
||||||
|
@ -889,73 +889,6 @@ def urlopen(request: urllib.request.Request):
|
|||||||
fetcher.conditional_fetch()
|
fetcher.conditional_fetch()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"root,deps,expected",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
True,
|
|
||||||
True,
|
|
||||||
[
|
|
||||||
"dttop",
|
|
||||||
"dtbuild1",
|
|
||||||
"dtbuild2",
|
|
||||||
"dtlink2",
|
|
||||||
"dtrun2",
|
|
||||||
"dtlink1",
|
|
||||||
"dtlink3",
|
|
||||||
"dtlink4",
|
|
||||||
"dtrun1",
|
|
||||||
"dtlink5",
|
|
||||||
"dtrun3",
|
|
||||||
"dtbuild3",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
False,
|
|
||||||
True,
|
|
||||||
[
|
|
||||||
"dtbuild1",
|
|
||||||
"dtbuild2",
|
|
||||||
"dtlink2",
|
|
||||||
"dtrun2",
|
|
||||||
"dtlink1",
|
|
||||||
"dtlink3",
|
|
||||||
"dtlink4",
|
|
||||||
"dtrun1",
|
|
||||||
"dtlink5",
|
|
||||||
"dtrun3",
|
|
||||||
"dtbuild3",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(True, False, ["dttop"]),
|
|
||||||
(False, False, []),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_correct_specs_are_pushed(
|
|
||||||
root, deps, expected, default_mock_concretization, tmpdir, temporary_store, monkeypatch
|
|
||||||
):
|
|
||||||
# Concretize dttop and add it to the temporary database (without prefixes)
|
|
||||||
spec = default_mock_concretization("dttop")
|
|
||||||
temporary_store.db.add(spec, directory_layout=None)
|
|
||||||
|
|
||||||
# Create a mirror push url
|
|
||||||
push_url = spack.mirror.Mirror.from_local_path(str(tmpdir)).push_url
|
|
||||||
|
|
||||||
packages_to_push = []
|
|
||||||
|
|
||||||
def fake_build_tarball(node, push_url, **kwargs):
|
|
||||||
assert push_url == push_url
|
|
||||||
assert not kwargs
|
|
||||||
assert isinstance(node, Spec)
|
|
||||||
packages_to_push.append(node.name)
|
|
||||||
|
|
||||||
monkeypatch.setattr(bindist, "_build_tarball", fake_build_tarball)
|
|
||||||
|
|
||||||
bindist.push([spec], push_url, include_root=root, include_dependencies=deps)
|
|
||||||
|
|
||||||
assert packages_to_push == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_reproducible_tarball_is_reproducible(tmpdir):
|
def test_reproducible_tarball_is_reproducible(tmpdir):
|
||||||
p = tmpdir.mkdir("prefix")
|
p = tmpdir.mkdir("prefix")
|
||||||
p.mkdir("bin")
|
p.mkdir("bin")
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import spack.binary_distribution
|
import spack.binary_distribution as bd
|
||||||
import spack.main
|
import spack.main
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.util.url
|
import spack.util.url
|
||||||
@ -26,22 +26,22 @@ def test_build_tarball_overwrite(install_mockery, mock_fetch, monkeypatch, tmpdi
|
|||||||
|
|
||||||
# Runs fine the first time, throws the second time
|
# Runs fine the first time, throws the second time
|
||||||
out_url = spack.util.url.path_to_file_url(str(tmpdir))
|
out_url = spack.util.url.path_to_file_url(str(tmpdir))
|
||||||
spack.binary_distribution._build_tarball(spec, out_url, unsigned=True)
|
bd.push_or_raise(spec, out_url, bd.PushOptions(unsigned=True))
|
||||||
with pytest.raises(spack.binary_distribution.NoOverwriteException):
|
with pytest.raises(bd.NoOverwriteException):
|
||||||
spack.binary_distribution._build_tarball(spec, out_url, unsigned=True)
|
bd.push_or_raise(spec, out_url, bd.PushOptions(unsigned=True))
|
||||||
|
|
||||||
# Should work fine with force=True
|
# Should work fine with force=True
|
||||||
spack.binary_distribution._build_tarball(spec, out_url, force=True, unsigned=True)
|
bd.push_or_raise(spec, out_url, bd.PushOptions(force=True, unsigned=True))
|
||||||
|
|
||||||
# Remove the tarball and try again.
|
# Remove the tarball and try again.
|
||||||
# This must *also* throw, because of the existing .spec.json file
|
# This must *also* throw, because of the existing .spec.json file
|
||||||
os.remove(
|
os.remove(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
spack.binary_distribution.build_cache_prefix("."),
|
bd.build_cache_prefix("."),
|
||||||
spack.binary_distribution.tarball_directory_name(spec),
|
bd.tarball_directory_name(spec),
|
||||||
spack.binary_distribution.tarball_name(spec, ".spack"),
|
bd.tarball_name(spec, ".spack"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(spack.binary_distribution.NoOverwriteException):
|
with pytest.raises(bd.NoOverwriteException):
|
||||||
spack.binary_distribution._build_tarball(spec, out_url, unsigned=True)
|
bd.push_or_raise(spec, out_url, bd.PushOptions(unsigned=True))
|
||||||
|
@ -476,16 +476,31 @@ def _fail(self, args):
|
|||||||
|
|
||||||
|
|
||||||
def test_ci_create_buildcache(tmpdir, working_env, config, mock_packages, monkeypatch):
|
def test_ci_create_buildcache(tmpdir, working_env, config, mock_packages, monkeypatch):
|
||||||
# Monkeypatching ci method tested elsewhere to reduce number of methods
|
"""Test that create_buildcache returns a list of objects with the correct
|
||||||
# that would need to be patched here.
|
keys and types."""
|
||||||
monkeypatch.setattr(spack.ci, "push_mirror_contents", lambda a, b, c, d: None)
|
monkeypatch.setattr(spack.ci, "push_mirror_contents", lambda a, b, c: True)
|
||||||
|
|
||||||
args = {
|
results = ci.create_buildcache(
|
||||||
"env": None,
|
None,
|
||||||
"buildcache_mirror_url": "file://fake-url",
|
pr_pipeline=True,
|
||||||
"pipeline_mirror_url": "file://fake-url",
|
buildcache_mirror_url="file:///fake-url-one",
|
||||||
}
|
pipeline_mirror_url="file:///fake-url-two",
|
||||||
ci.create_buildcache(**args)
|
)
|
||||||
|
|
||||||
|
assert len(results) == 2
|
||||||
|
result1, result2 = results
|
||||||
|
assert result1.success
|
||||||
|
assert result1.url == "file:///fake-url-one"
|
||||||
|
assert result2.success
|
||||||
|
assert result2.url == "file:///fake-url-two"
|
||||||
|
|
||||||
|
results = ci.create_buildcache(
|
||||||
|
None, pr_pipeline=True, buildcache_mirror_url="file:///fake-url-one"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(results) == 1
|
||||||
|
assert results[0].success
|
||||||
|
assert results[0].url == "file:///fake-url-one"
|
||||||
|
|
||||||
|
|
||||||
def test_ci_run_standalone_tests_missing_requirements(
|
def test_ci_run_standalone_tests_missing_requirements(
|
||||||
|
@ -267,3 +267,70 @@ def test_buildcache_create_install(
|
|||||||
tarball = spack.binary_distribution.tarball_name(spec, ".spec.json")
|
tarball = spack.binary_distribution.tarball_name(spec, ".spec.json")
|
||||||
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball_path))
|
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball_path))
|
||||||
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball))
|
assert os.path.exists(os.path.join(str(tmpdir), "build_cache", tarball))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"things_to_install,expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
[
|
||||||
|
"dttop",
|
||||||
|
"dtbuild1",
|
||||||
|
"dtbuild2",
|
||||||
|
"dtlink2",
|
||||||
|
"dtrun2",
|
||||||
|
"dtlink1",
|
||||||
|
"dtlink3",
|
||||||
|
"dtlink4",
|
||||||
|
"dtrun1",
|
||||||
|
"dtlink5",
|
||||||
|
"dtrun3",
|
||||||
|
"dtbuild3",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dependencies",
|
||||||
|
[
|
||||||
|
"dtbuild1",
|
||||||
|
"dtbuild2",
|
||||||
|
"dtlink2",
|
||||||
|
"dtrun2",
|
||||||
|
"dtlink1",
|
||||||
|
"dtlink3",
|
||||||
|
"dtlink4",
|
||||||
|
"dtrun1",
|
||||||
|
"dtlink5",
|
||||||
|
"dtrun3",
|
||||||
|
"dtbuild3",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("package", ["dttop"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_correct_specs_are_pushed(
|
||||||
|
things_to_install, expected, tmpdir, monkeypatch, default_mock_concretization, temporary_store
|
||||||
|
):
|
||||||
|
# Concretize dttop and add it to the temporary database (without prefixes)
|
||||||
|
spec = default_mock_concretization("dttop")
|
||||||
|
temporary_store.db.add(spec, directory_layout=None)
|
||||||
|
slash_hash = "/{0}".format(spec.dag_hash())
|
||||||
|
|
||||||
|
packages_to_push = []
|
||||||
|
|
||||||
|
def fake_push(node, push_url, options):
|
||||||
|
assert isinstance(node, Spec)
|
||||||
|
packages_to_push.append(node.name)
|
||||||
|
|
||||||
|
monkeypatch.setattr(spack.binary_distribution, "push_or_raise", fake_push)
|
||||||
|
|
||||||
|
buildcache_create_args = ["create", "-d", str(tmpdir), "--unsigned"]
|
||||||
|
|
||||||
|
if things_to_install != "":
|
||||||
|
buildcache_create_args.extend(["--only", things_to_install])
|
||||||
|
|
||||||
|
buildcache_create_args.extend([slash_hash])
|
||||||
|
|
||||||
|
buildcache(*buildcache_create_args)
|
||||||
|
|
||||||
|
assert packages_to_push == expected
|
||||||
|
@ -1240,7 +1240,7 @@ def test_push_mirror_contents(
|
|||||||
|
|
||||||
with tmpdir.as_cwd():
|
with tmpdir.as_cwd():
|
||||||
env_cmd("create", "test", "./spack.yaml")
|
env_cmd("create", "test", "./spack.yaml")
|
||||||
with ev.read("test") as env:
|
with ev.read("test"):
|
||||||
concrete_spec = Spec("patchelf").concretized()
|
concrete_spec = Spec("patchelf").concretized()
|
||||||
spec_json = concrete_spec.to_json(hash=ht.dag_hash)
|
spec_json = concrete_spec.to_json(hash=ht.dag_hash)
|
||||||
json_path = str(tmpdir.join("spec.json"))
|
json_path = str(tmpdir.join("spec.json"))
|
||||||
@ -1249,8 +1249,7 @@ def test_push_mirror_contents(
|
|||||||
|
|
||||||
install_cmd("--add", "--keep-stage", json_path)
|
install_cmd("--add", "--keep-stage", json_path)
|
||||||
|
|
||||||
# env, spec, json_path, mirror_url, build_id, sign_binaries
|
ci.push_mirror_contents(concrete_spec, mirror_url, True)
|
||||||
ci.push_mirror_contents(env, json_path, mirror_url, True)
|
|
||||||
|
|
||||||
buildcache_path = os.path.join(mirror_dir.strpath, "build_cache")
|
buildcache_path = os.path.join(mirror_dir.strpath, "build_cache")
|
||||||
|
|
||||||
@ -1348,7 +1347,7 @@ def failing_access(*args, **kwargs):
|
|||||||
|
|
||||||
# Input doesn't matter, as wwe are faking exceptional output
|
# Input doesn't matter, as wwe are faking exceptional output
|
||||||
url = "fakejunk"
|
url = "fakejunk"
|
||||||
ci.push_mirror_contents(None, None, url, None)
|
ci.push_mirror_contents(None, url, None)
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
std_out = captured[0]
|
std_out = captured[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user