James Ennis pushed to branch jennis/refactor_artifact_log at BuildStream / buildstream
Commits:
-
cf5b906f
by James Ennis at 2019-01-23T12:52:54Z
-
7b3c1c05
by James Ennis at 2019-01-23T12:52:54Z
-
0eb067f7
by James Ennis at 2019-01-23T12:52:54Z
-
b0eda0a0
by James Ennis at 2019-01-23T14:44:42Z
-
c7cd8c7e
by James Ennis at 2019-01-23T14:44:42Z
-
4d7c408c
by James Ennis at 2019-01-23T14:44:42Z
-
2d439bd8
by James Ennis at 2019-01-23T14:44:42Z
-
42f3f456
by James Ennis at 2019-01-23T14:44:42Z
9 changed files:
- buildstream/_artifactcache.py
- + buildstream/_artifactelement.py
- buildstream/_exceptions.py
- buildstream/_frontend/cli.py
- buildstream/_project.py
- buildstream/_stream.py
- buildstream/element.py
- tests/artifactcache/pull.py
- tests/artifactcache/push.py
Changes:
| ... | ... | @@ -19,7 +19,6 @@ |
| 19 | 19 |
|
| 20 | 20 |
import multiprocessing
|
| 21 | 21 |
import os
|
| 22 |
-import string
|
|
| 23 | 22 |
from collections.abc import Mapping
|
| 24 | 23 |
|
| 25 | 24 |
from .types import _KeyStrength
|
| ... | ... | @@ -77,37 +76,6 @@ class ArtifactCache(): |
| 77 | 76 |
|
| 78 | 77 |
self._calculate_cache_quota()
|
| 79 | 78 |
|
| 80 |
- # get_artifact_fullname()
|
|
| 81 |
- #
|
|
| 82 |
- # Generate a full name for an artifact, including the
|
|
| 83 |
- # project namespace, element name and cache key.
|
|
| 84 |
- #
|
|
| 85 |
- # This can also be used as a relative path safely, and
|
|
| 86 |
- # will normalize parts of the element name such that only
|
|
| 87 |
- # digits, letters and some select characters are allowed.
|
|
| 88 |
- #
|
|
| 89 |
- # Args:
|
|
| 90 |
- # element (Element): The Element object
|
|
| 91 |
- # key (str): The element's cache key
|
|
| 92 |
- #
|
|
| 93 |
- # Returns:
|
|
| 94 |
- # (str): The relative path for the artifact
|
|
| 95 |
- #
|
|
| 96 |
- def get_artifact_fullname(self, element, key):
|
|
| 97 |
- project = element._get_project()
|
|
| 98 |
- |
|
| 99 |
- # Normalize ostree ref unsupported chars
|
|
| 100 |
- valid_chars = string.digits + string.ascii_letters + '-._'
|
|
| 101 |
- element_name = ''.join([
|
|
| 102 |
- x if x in valid_chars else '_'
|
|
| 103 |
- for x in element.normal_name
|
|
| 104 |
- ])
|
|
| 105 |
- |
|
| 106 |
- assert key is not None
|
|
| 107 |
- |
|
| 108 |
- # assume project and element names are not allowed to contain slashes
|
|
| 109 |
- return '{0}/{1}/{2}'.format(project.name, element_name, key)
|
|
| 110 |
- |
|
| 111 | 79 |
# setup_remotes():
|
| 112 | 80 |
#
|
| 113 | 81 |
# Sets up which remotes to use
|
| ... | ... | @@ -206,7 +174,7 @@ class ArtifactCache(): |
| 206 | 174 |
for key in (strong_key, weak_key):
|
| 207 | 175 |
if key:
|
| 208 | 176 |
try:
|
| 209 |
- ref = self.get_artifact_fullname(element, key)
|
|
| 177 |
+ ref = element.get_artifact_name(key)
|
|
| 210 | 178 |
|
| 211 | 179 |
self.cas.update_mtime(ref)
|
| 212 | 180 |
except CASError:
|
| ... | ... | @@ -417,7 +385,7 @@ class ArtifactCache(): |
| 417 | 385 |
# Returns: True if the artifact is in the cache, False otherwise
|
| 418 | 386 |
#
|
| 419 | 387 |
def contains(self, element, key):
|
| 420 |
- ref = self.get_artifact_fullname(element, key)
|
|
| 388 |
+ ref = element.get_artifact_name(key)
|
|
| 421 | 389 |
|
| 422 | 390 |
return self.cas.contains(ref)
|
| 423 | 391 |
|
| ... | ... | @@ -434,7 +402,7 @@ class ArtifactCache(): |
| 434 | 402 |
# Returns: True if the subdir exists & is populated in the cache, False otherwise
|
| 435 | 403 |
#
|
| 436 | 404 |
def contains_subdir_artifact(self, element, key, subdir):
|
| 437 |
- ref = self.get_artifact_fullname(element, key)
|
|
| 405 |
+ ref = element.get_artifact_name(key)
|
|
| 438 | 406 |
return self.cas.contains_subdir_artifact(ref, subdir)
|
| 439 | 407 |
|
| 440 | 408 |
# list_artifacts():
|
| ... | ... | @@ -442,8 +410,7 @@ class ArtifactCache(): |
| 442 | 410 |
# List artifacts in this cache in LRU order.
|
| 443 | 411 |
#
|
| 444 | 412 |
# Returns:
|
| 445 |
- # ([str]) - A list of artifact names as generated by
|
|
| 446 |
- # `ArtifactCache.get_artifact_fullname` in LRU order
|
|
| 413 |
+ # ([str]) - A list of artifact names as generated in LRU order
|
|
| 447 | 414 |
#
|
| 448 | 415 |
def list_artifacts(self):
|
| 449 | 416 |
return self.cas.list_refs()
|
| ... | ... | @@ -455,8 +422,7 @@ class ArtifactCache(): |
| 455 | 422 |
#
|
| 456 | 423 |
# Args:
|
| 457 | 424 |
# ref (artifact_name): The name of the artifact to remove (as
|
| 458 |
- # generated by
|
|
| 459 |
- # `ArtifactCache.get_artifact_fullname`)
|
|
| 425 |
+ # generated by `Element.get_artifact_name`)
|
|
| 460 | 426 |
#
|
| 461 | 427 |
# Returns:
|
| 462 | 428 |
# (int|None) The amount of space pruned from the repository in
|
| ... | ... | @@ -503,7 +469,7 @@ class ArtifactCache(): |
| 503 | 469 |
# Returns: path to extracted artifact
|
| 504 | 470 |
#
|
| 505 | 471 |
def extract(self, element, key, subdir=None):
|
| 506 |
- ref = self.get_artifact_fullname(element, key)
|
|
| 472 |
+ ref = element.get_artifact_name(key)
|
|
| 507 | 473 |
|
| 508 | 474 |
path = os.path.join(self.extractdir, element._get_project().name, element.normal_name)
|
| 509 | 475 |
|
| ... | ... | @@ -519,7 +485,7 @@ class ArtifactCache(): |
| 519 | 485 |
# keys (list): The cache keys to use
|
| 520 | 486 |
#
|
| 521 | 487 |
def commit(self, element, content, keys):
|
| 522 |
- refs = [self.get_artifact_fullname(element, key) for key in keys]
|
|
| 488 |
+ refs = [element.get_artifact_name(key) for key in keys]
|
|
| 523 | 489 |
|
| 524 | 490 |
self.cas.commit(refs, content)
|
| 525 | 491 |
|
| ... | ... | @@ -535,8 +501,8 @@ class ArtifactCache(): |
| 535 | 501 |
# subdir (str): A subdirectory to limit the comparison to
|
| 536 | 502 |
#
|
| 537 | 503 |
def diff(self, element, key_a, key_b, *, subdir=None):
|
| 538 |
- ref_a = self.get_artifact_fullname(element, key_a)
|
|
| 539 |
- ref_b = self.get_artifact_fullname(element, key_b)
|
|
| 504 |
+ ref_a = element.get_artifact_name(key_a)
|
|
| 505 |
+ ref_b = element.get_artifact_name(key_b)
|
|
| 540 | 506 |
|
| 541 | 507 |
return self.cas.diff(ref_a, ref_b, subdir=subdir)
|
| 542 | 508 |
|
| ... | ... | @@ -597,7 +563,7 @@ class ArtifactCache(): |
| 597 | 563 |
# (ArtifactError): if there was an error
|
| 598 | 564 |
#
|
| 599 | 565 |
def push(self, element, keys):
|
| 600 |
- refs = [self.get_artifact_fullname(element, key) for key in list(keys)]
|
|
| 566 |
+ refs = [element.get_artifact_name(key) for key in list(keys)]
|
|
| 601 | 567 |
|
| 602 | 568 |
project = element._get_project()
|
| 603 | 569 |
|
| ... | ... | @@ -635,7 +601,7 @@ class ArtifactCache(): |
| 635 | 601 |
# (bool): True if pull was successful, False if artifact was not available
|
| 636 | 602 |
#
|
| 637 | 603 |
def pull(self, element, key, *, progress=None, subdir=None, excluded_subdirs=None):
|
| 638 |
- ref = self.get_artifact_fullname(element, key)
|
|
| 604 |
+ ref = element.get_artifact_name(key)
|
|
| 639 | 605 |
|
| 640 | 606 |
project = element._get_project()
|
| 641 | 607 |
|
| ... | ... | @@ -747,8 +713,8 @@ class ArtifactCache(): |
| 747 | 713 |
# newkey (str): A new cache key for the artifact
|
| 748 | 714 |
#
|
| 749 | 715 |
def link_key(self, element, oldkey, newkey):
|
| 750 |
- oldref = self.get_artifact_fullname(element, oldkey)
|
|
| 751 |
- newref = self.get_artifact_fullname(element, newkey)
|
|
| 716 |
+ oldref = element.get_artifact_name(oldkey)
|
|
| 717 |
+ newref = element.get_artifact_name(newkey)
|
|
| 752 | 718 |
|
| 753 | 719 |
self.cas.link_ref(oldref, newref)
|
| 754 | 720 |
|
| 1 |
+#
|
|
| 2 |
+# Copyright (C) 2019 Bloomberg Finance LP
|
|
| 3 |
+#
|
|
| 4 |
+# This program is free software; you can redistribute it and/or
|
|
| 5 |
+# modify it under the terms of the GNU Lesser General Public
|
|
| 6 |
+# License as published by the Free Software Foundation; either
|
|
| 7 |
+# version 2 of the License, or (at your option) any later version.
|
|
| 8 |
+#
|
|
| 9 |
+# This library is distributed in the hope that it will be useful,
|
|
| 10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
| 12 |
+# Lesser General Public License for more details.
|
|
| 13 |
+#
|
|
| 14 |
+# You should have received a copy of the GNU Lesser General Public
|
|
| 15 |
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
| 16 |
+#
|
|
| 17 |
+# Authors:
|
|
| 18 |
+# James Ennis <james ennis codethink co uk>
|
|
| 19 |
+from ._exceptions import ArtifactElementError
|
|
| 20 |
+ |
|
| 21 |
+ |
|
| 22 |
+# ArtifactElement()
|
|
| 23 |
+#
|
|
| 24 |
+# Object to be used for directly processing an artifact
|
|
| 25 |
+#
|
|
| 26 |
+# Args:
|
|
| 27 |
+# context (Context): The Context object
|
|
| 28 |
+# ref (str): The artifact ref
|
|
| 29 |
+#
|
|
| 30 |
+class ArtifactElement():
|
|
| 31 |
+ def __init__(self, context, ref):
|
|
| 32 |
+ try:
|
|
| 33 |
+ project_name, element, key = ref.split('/', 2)
|
|
| 34 |
+ except ValueError:
|
|
| 35 |
+ raise ArtifactElementError("Artifact: {} is not of the expected format".format(ref))
|
|
| 36 |
+ |
|
| 37 |
+ self.ref = ref
|
|
| 38 |
+ self.project_name = project_name
|
|
| 39 |
+ self.element = element
|
|
| 40 |
+ self.key = key
|
|
| 41 |
+ |
|
| 42 |
+ self._context = context
|
|
| 43 |
+ self._artifacts = context.artifactcache
|
|
| 44 |
+ self._cas = context.artifactcache.cas # is this bad...?
|
|
| 45 |
+ |
|
| 46 |
+ def get_artifact_name(self):
|
|
| 47 |
+ return self.ref
|
|
| 48 |
+ |
|
| 49 |
+ def _cached(self):
|
|
| 50 |
+ return self._cas.contains(self.ref)
|
| ... | ... | @@ -344,3 +344,12 @@ class AppError(BstError): |
| 344 | 344 |
#
|
| 345 | 345 |
class SkipJob(Exception):
|
| 346 | 346 |
pass
|
| 347 |
+ |
|
| 348 |
+ |
|
| 349 |
+# ArtifactElementError
|
|
| 350 |
+#
|
|
| 351 |
+# Raised when errors are encountered by artifact elements
|
|
| 352 |
+#
|
|
| 353 |
+class ArtifactElementError(BstError):
|
|
| 354 |
+ def __init__(self, message, *, detail=None, reason=None, temporary=False):
|
|
| 355 |
+ super().__init__(message, detail=detail, domain=ErrorDomain.ELEMENT, reason=reason, temporary=True)
|
| 1 | 1 |
import os
|
| 2 | 2 |
import sys
|
| 3 | 3 |
from contextlib import ExitStack
|
| 4 |
-from fnmatch import fnmatch
|
|
| 5 | 4 |
from functools import partial
|
| 6 | 5 |
from tempfile import TemporaryDirectory
|
| 7 | 6 |
|
| ... | ... | @@ -859,38 +858,6 @@ def workspace_list(app): |
| 859 | 858 |
#############################################################
|
| 860 | 859 |
# Artifact Commands #
|
| 861 | 860 |
#############################################################
|
| 862 |
-def _classify_artifacts(names, cas, project_directory):
|
|
| 863 |
- element_targets = []
|
|
| 864 |
- artifact_refs = []
|
|
| 865 |
- element_globs = []
|
|
| 866 |
- artifact_globs = []
|
|
| 867 |
- |
|
| 868 |
- for name in names:
|
|
| 869 |
- if name.endswith('.bst'):
|
|
| 870 |
- if any(c in "*?[" for c in name):
|
|
| 871 |
- element_globs.append(name)
|
|
| 872 |
- else:
|
|
| 873 |
- element_targets.append(name)
|
|
| 874 |
- else:
|
|
| 875 |
- if any(c in "*?[" for c in name):
|
|
| 876 |
- artifact_globs.append(name)
|
|
| 877 |
- else:
|
|
| 878 |
- artifact_refs.append(name)
|
|
| 879 |
- |
|
| 880 |
- if element_globs:
|
|
| 881 |
- for dirpath, _, filenames in os.walk(project_directory):
|
|
| 882 |
- for filename in filenames:
|
|
| 883 |
- element_path = os.path.join(dirpath, filename).lstrip(project_directory).lstrip('/')
|
|
| 884 |
- if any(fnmatch(element_path, glob) for glob in element_globs):
|
|
| 885 |
- element_targets.append(element_path)
|
|
| 886 |
- |
|
| 887 |
- if artifact_globs:
|
|
| 888 |
- artifact_refs.extend(ref for ref in cas.list_refs()
|
|
| 889 |
- if any(fnmatch(ref, glob) for glob in artifact_globs))
|
|
| 890 |
- |
|
| 891 |
- return element_targets, artifact_refs
|
|
| 892 |
- |
|
| 893 |
- |
|
| 894 | 861 |
@cli.group(short_help="Manipulate cached artifacts")
|
| 895 | 862 |
def artifact():
|
| 896 | 863 |
"""Manipulate cached artifacts"""
|
| ... | ... | @@ -1045,53 +1012,31 @@ def artifact_push(app, elements, deps, remote): |
| 1045 | 1012 |
@click.pass_obj
|
| 1046 | 1013 |
def artifact_log(app, artifacts):
|
| 1047 | 1014 |
"""Show logs of all artifacts"""
|
| 1048 |
- from .._exceptions import CASError
|
|
| 1049 |
- from .._message import MessageType
|
|
| 1050 |
- from .._pipeline import PipelineSelection
|
|
| 1051 |
- from ..storage._casbaseddirectory import CasBasedDirectory
|
|
| 1052 |
- |
|
| 1053 |
- with ExitStack() as stack:
|
|
| 1054 |
- stack.enter_context(app.initialized())
|
|
| 1055 |
- cache = app.context.artifactcache
|
|
| 1015 |
+ # Guess the element if we're in a workspace
|
|
| 1016 |
+ if not artifacts:
|
|
| 1017 |
+ guessed_target = app.context.guess_element()
|
|
| 1018 |
+ if guessed_target:
|
|
| 1019 |
+ artifacts = [guessed_target]
|
|
| 1056 | 1020 |
|
| 1057 |
- elements, artifacts = _classify_artifacts(artifacts, cache.cas,
|
|
| 1058 |
- app.project.directory)
|
|
| 1059 |
- |
|
| 1060 |
- vdirs = []
|
|
| 1061 |
- extractdirs = []
|
|
| 1062 |
- if artifacts:
|
|
| 1063 |
- for ref in artifacts:
|
|
| 1064 |
- try:
|
|
| 1065 |
- cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
|
|
| 1066 |
- vdir = CasBasedDirectory(cache.cas, cache_id)
|
|
| 1067 |
- vdirs.append(vdir)
|
|
| 1068 |
- except CASError as e:
|
|
| 1069 |
- app._message(MessageType.WARN, "Artifact {} is not cached".format(ref), detail=str(e))
|
|
| 1070 |
- continue
|
|
| 1071 |
- if elements:
|
|
| 1072 |
- elements = app.stream.load_selection(elements, selection=PipelineSelection.NONE)
|
|
| 1073 |
- for element in elements:
|
|
| 1074 |
- if not element._cached():
|
|
| 1075 |
- app._message(MessageType.WARN, "Element {} is not cached".format(element))
|
|
| 1076 |
- continue
|
|
| 1077 |
- ref = cache.get_artifact_fullname(element, element._get_cache_key())
|
|
| 1078 |
- cache_id = cache.cas.resolve_ref(ref, update_mtime=True)
|
|
| 1079 |
- vdir = CasBasedDirectory(cache.cas, cache_id)
|
|
| 1080 |
- vdirs.append(vdir)
|
|
| 1081 |
- |
|
| 1082 |
- for vdir in vdirs:
|
|
| 1083 |
- # NOTE: If reading the logs feels unresponsive, here would be a good place to provide progress information.
|
|
| 1084 |
- logsdir = vdir.descend(["logs"])
|
|
| 1085 |
- td = stack.enter_context(TemporaryDirectory())
|
|
| 1086 |
- logsdir.export_files(td, can_link=True)
|
|
| 1087 |
- extractdirs.append(td)
|
|
| 1088 |
- |
|
| 1089 |
- for extractdir in extractdirs:
|
|
| 1090 |
- for log in (os.path.join(extractdir, log) for log in os.listdir(extractdir)):
|
|
| 1091 |
- # NOTE: Should click gain the ability to pass files to the pager this can be optimised.
|
|
| 1092 |
- with open(log) as f:
|
|
| 1093 |
- data = f.read()
|
|
| 1094 |
- click.echo_via_pager(data)
|
|
| 1021 |
+ with app.initialized():
|
|
| 1022 |
+ vdirs = app.stream.artifact_log(artifacts)
|
|
| 1023 |
+ |
|
| 1024 |
+ with ExitStack() as stack:
|
|
| 1025 |
+ extractdirs = []
|
|
| 1026 |
+ for vdir in vdirs:
|
|
| 1027 |
+ # NOTE: If reading the logs feels unresponsive, here would be a good place
|
|
| 1028 |
+ # to provide progress information.
|
|
| 1029 |
+ logsdir = vdir.descend(["logs"])
|
|
| 1030 |
+ td = stack.enter_context(TemporaryDirectory())
|
|
| 1031 |
+ logsdir.export_files(td, can_link=True)
|
|
| 1032 |
+ extractdirs.append(td)
|
|
| 1033 |
+ |
|
| 1034 |
+ for extractdir in extractdirs:
|
|
| 1035 |
+ for log in (os.path.join(extractdir, log) for log in os.listdir(extractdir)):
|
|
| 1036 |
+ # NOTE: Should click gain the ability to pass files to the pager this can be optimised.
|
|
| 1037 |
+ with open(log) as f:
|
|
| 1038 |
+ data = f.read()
|
|
| 1039 |
+ click.echo_via_pager(data)
|
|
| 1095 | 1040 |
|
| 1096 | 1041 |
|
| 1097 | 1042 |
##################################################################
|
| ... | ... | @@ -26,6 +26,7 @@ from . import utils |
| 26 | 26 |
from . import _cachekey
|
| 27 | 27 |
from . import _site
|
| 28 | 28 |
from . import _yaml
|
| 29 |
+from ._artifactelement import ArtifactElement
|
|
| 29 | 30 |
from ._profile import Topics, profile_start, profile_end
|
| 30 | 31 |
from ._exceptions import LoadError, LoadErrorReason
|
| 31 | 32 |
from ._options import OptionPool
|
| ... | ... | @@ -252,6 +253,19 @@ class Project(): |
| 252 | 253 |
else:
|
| 253 | 254 |
return self.config.element_factory.create(self._context, self, meta)
|
| 254 | 255 |
|
| 256 |
+ # create_artifact_element()
|
|
| 257 |
+ #
|
|
| 258 |
+ # Instantiate and return an ArtifactElement
|
|
| 259 |
+ #
|
|
| 260 |
+ # Args:
|
|
| 261 |
+ # ref (str): A string of the artifact ref
|
|
| 262 |
+ #
|
|
| 263 |
+ # Returns:
|
|
| 264 |
+ # (ArtifactElement): A newly created ArtifactElement object of the appropriate kind
|
|
| 265 |
+ #
|
|
| 266 |
+ def create_artifact_element(self, ref):
|
|
| 267 |
+ return ArtifactElement(self._context, ref)
|
|
| 268 |
+ |
|
| 255 | 269 |
# create_source()
|
| 256 | 270 |
#
|
| 257 | 271 |
# Instantiate and return a Source
|
| ... | ... | @@ -27,6 +27,7 @@ import shutil |
| 27 | 27 |
import tarfile
|
| 28 | 28 |
import tempfile
|
| 29 | 29 |
from contextlib import contextmanager, suppress
|
| 30 |
+from fnmatch import fnmatch
|
|
| 30 | 31 |
|
| 31 | 32 |
from ._exceptions import StreamError, ImplError, BstError, set_last_task_error
|
| 32 | 33 |
from ._message import Message, MessageType
|
| ... | ... | @@ -439,6 +440,53 @@ class Stream(): |
| 439 | 440 |
raise StreamError("Error while staging dependencies into a sandbox"
|
| 440 | 441 |
": '{}'".format(e), detail=e.detail, reason=e.reason) from e
|
| 441 | 442 |
|
| 443 |
+ # artifact_log()
|
|
| 444 |
+ #
|
|
| 445 |
+ # Show the full log of an artifact
|
|
| 446 |
+ #
|
|
| 447 |
+ # Args:
|
|
| 448 |
+ # targets (str): Targets to view the logs of
|
|
| 449 |
+ #
|
|
| 450 |
+ # Returns:
|
|
| 451 |
+ # vdirs (list): A list of CasBasedDirectory objects
|
|
| 452 |
+ #
|
|
| 453 |
+ def artifact_log(self, targets):
|
|
| 454 |
+ from .storage._casbaseddirectory import CasBasedDirectory # is this bad?
|
|
| 455 |
+ |
|
| 456 |
+ cas = self._artifacts.cas # Is this bad?
|
|
| 457 |
+ cached_refs = cas.list_refs()
|
|
| 458 |
+ project_dir = self._project.directory
|
|
| 459 |
+ |
|
| 460 |
+ # Distinguish the artifacts from the elements
|
|
| 461 |
+ elements, artifacts = self._classify_artifacts(targets, cached_refs, project_dir)
|
|
| 462 |
+ |
|
| 463 |
+ # Obtain Element objects
|
|
| 464 |
+ if elements:
|
|
| 465 |
+ elements = self.load_selection(elements, selection=PipelineSelection.NONE)
|
|
| 466 |
+ |
|
| 467 |
+ # Obtain ArtifactElement objects
|
|
| 468 |
+ artifact_elements = []
|
|
| 469 |
+ if artifacts:
|
|
| 470 |
+ for ref in artifacts:
|
|
| 471 |
+ artifact_element = self._project.create_artifact_element(ref)
|
|
| 472 |
+ artifact_elements.append(artifact_element)
|
|
| 473 |
+ |
|
| 474 |
+ # Concatenate the lists
|
|
| 475 |
+ objects = elements + artifact_elements
|
|
| 476 |
+ |
|
| 477 |
+ vdirs = []
|
|
| 478 |
+ for obj in objects:
|
|
| 479 |
+ ref = obj.get_artifact_name()
|
|
| 480 |
+ if not obj._cached():
|
|
| 481 |
+ self._message(MessageType.WARN, "{} is not cached".format(ref))
|
|
| 482 |
+ continue
|
|
| 483 |
+ |
|
| 484 |
+ cache_id = cas.resolve_ref(ref, update_mtime=True)
|
|
| 485 |
+ vdir = CasBasedDirectory(cas, cache_id)
|
|
| 486 |
+ vdirs.append(vdir)
|
|
| 487 |
+ |
|
| 488 |
+ return vdirs
|
|
| 489 |
+ |
|
| 442 | 490 |
# source_checkout()
|
| 443 | 491 |
#
|
| 444 | 492 |
# Checkout sources of the target element to the specified location
|
| ... | ... | @@ -1273,3 +1321,47 @@ class Stream(): |
| 1273 | 1321 |
required_list.append(element)
|
| 1274 | 1322 |
|
| 1275 | 1323 |
return required_list
|
| 1324 |
+ |
|
| 1325 |
+ # _classify_artifacts()
|
|
| 1326 |
+ #
|
|
| 1327 |
+ # Split up a list of tagets into element names and artifact refs
|
|
| 1328 |
+ #
|
|
| 1329 |
+ # Args:
|
|
| 1330 |
+ # names (list): A list of targets
|
|
| 1331 |
+ # cached (list): A list of locally cached refs
|
|
| 1332 |
+ # project_directory (str): Absolute path to the project
|
|
| 1333 |
+ #
|
|
| 1334 |
+ # Returns:
|
|
| 1335 |
+ # (list): element names present in the targets
|
|
| 1336 |
+ # (list): artifact refs present in the targets
|
|
| 1337 |
+ #
|
|
| 1338 |
+ def _classify_artifacts(self, names, cached, project_directory):
|
|
| 1339 |
+ element_targets = []
|
|
| 1340 |
+ artifact_refs = []
|
|
| 1341 |
+ element_globs = []
|
|
| 1342 |
+ artifact_globs = []
|
|
| 1343 |
+ |
|
| 1344 |
+ for name in names:
|
|
| 1345 |
+ if name.endswith('.bst'):
|
|
| 1346 |
+ if any(c in "*?[" for c in name):
|
|
| 1347 |
+ element_globs.append(name)
|
|
| 1348 |
+ else:
|
|
| 1349 |
+ element_targets.append(name)
|
|
| 1350 |
+ else:
|
|
| 1351 |
+ if any(c in "*?[" for c in name):
|
|
| 1352 |
+ artifact_globs.append(name)
|
|
| 1353 |
+ else:
|
|
| 1354 |
+ artifact_refs.append(name)
|
|
| 1355 |
+ |
|
| 1356 |
+ if element_globs:
|
|
| 1357 |
+ for dirpath, _, filenames in os.walk(project_directory):
|
|
| 1358 |
+ for filename in filenames:
|
|
| 1359 |
+ element_path = os.path.join(dirpath, filename).lstrip(project_directory).lstrip('/')
|
|
| 1360 |
+ if any(fnmatch(element_path, glob) for glob in element_globs):
|
|
| 1361 |
+ element_targets.append(element_path)
|
|
| 1362 |
+ |
|
| 1363 |
+ if artifact_globs:
|
|
| 1364 |
+ artifact_refs.extend(ref for ref in cached
|
|
| 1365 |
+ if any(fnmatch(ref, glob) for glob in artifact_globs))
|
|
| 1366 |
+ |
|
| 1367 |
+ return element_targets, artifact_refs
|
| ... | ... | @@ -82,6 +82,7 @@ import contextlib |
| 82 | 82 |
from contextlib import contextmanager
|
| 83 | 83 |
import tempfile
|
| 84 | 84 |
import shutil
|
| 85 |
+import string
|
|
| 85 | 86 |
|
| 86 | 87 |
from . import _yaml
|
| 87 | 88 |
from ._variables import Variables
|
| ... | ... | @@ -577,6 +578,37 @@ class Element(Plugin): |
| 577 | 578 |
self.__assert_cached()
|
| 578 | 579 |
return self.__compute_splits(include, exclude, orphans)
|
| 579 | 580 |
|
| 581 |
+ def get_artifact_name(self, key=None):
|
|
| 582 |
+ """Compute and return this element's full artifact name
|
|
| 583 |
+ |
|
| 584 |
+ Generate a full name for an artifact, including the project
|
|
| 585 |
+ namespace, element name and cache key.
|
|
| 586 |
+ |
|
| 587 |
+ This can also be used as a relative path safely, and
|
|
| 588 |
+ will normalize parts of the element name such that only
|
|
| 589 |
+ digits, letters and some select characters are allowed.
|
|
| 590 |
+ |
|
| 591 |
+ Args:
|
|
| 592 |
+ key (str): The element's cache key. Defaults to None
|
|
| 593 |
+ |
|
| 594 |
+ Returns:
|
|
| 595 |
+ (str): The relative path for the artifact
|
|
| 596 |
+ """
|
|
| 597 |
+ project = self._get_project()
|
|
| 598 |
+ if key is None:
|
|
| 599 |
+ key = self._get_cache_key()
|
|
| 600 |
+ |
|
| 601 |
+ assert key is not None
|
|
| 602 |
+ |
|
| 603 |
+ valid_chars = string.digits + string.ascii_letters + '-._'
|
|
| 604 |
+ element_name = ''.join([
|
|
| 605 |
+ x if x in valid_chars else '_'
|
|
| 606 |
+ for x in self.normal_name
|
|
| 607 |
+ ])
|
|
| 608 |
+ |
|
| 609 |
+ # assume project and element names are not allowed to contain slashes
|
|
| 610 |
+ return '{0}/{1}/{2}'.format(project.name, element_name, key)
|
|
| 611 |
+ |
|
| 580 | 612 |
def stage_artifact(self, sandbox, *, path=None, include=None, exclude=None, orphans=True, update_mtimes=None):
|
| 581 | 613 |
"""Stage this element's output artifact in the sandbox
|
| 582 | 614 |
|
| ... | ... | @@ -210,7 +210,7 @@ def test_pull_tree(cli, tmpdir, datafiles): |
| 210 | 210 |
assert artifactcache.contains(element, element_key)
|
| 211 | 211 |
|
| 212 | 212 |
# Retrieve the Directory object from the cached artifact
|
| 213 |
- artifact_ref = artifactcache.get_artifact_fullname(element, element_key)
|
|
| 213 |
+ artifact_ref = element.get_artifact_name(element_key)
|
|
| 214 | 214 |
artifact_digest = cas.resolve_ref(artifact_ref)
|
| 215 | 215 |
|
| 216 | 216 |
queue = multiprocessing.Queue()
|
| ... | ... | @@ -190,7 +190,7 @@ def test_push_directory(cli, tmpdir, datafiles): |
| 190 | 190 |
assert artifactcache.has_push_remotes(element=element)
|
| 191 | 191 |
|
| 192 | 192 |
# Recreate the CasBasedDirectory object from the cached artifact
|
| 193 |
- artifact_ref = artifactcache.get_artifact_fullname(element, element_key)
|
|
| 193 |
+ artifact_ref = element.get_artifact_name(element_key)
|
|
| 194 | 194 |
artifact_digest = cas.resolve_ref(artifact_ref)
|
| 195 | 195 |
|
| 196 | 196 |
queue = multiprocessing.Queue()
|
