jolt 0.9.328__tar.gz → 0.9.332__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jolt-0.9.328 → jolt-0.9.332}/PKG-INFO +10 -10
- {jolt-0.9.328 → jolt-0.9.332}/jolt/cache.py +26 -8
- {jolt-0.9.328 → jolt-0.9.332}/jolt/graph.py +163 -27
- {jolt-0.9.328 → jolt-0.9.332}/jolt/log.py +39 -10
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/docker.py +8 -7
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/podman.py +87 -36
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/scheduler.py +7 -3
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/selfdeploy.py +1 -1
- {jolt-0.9.328 → jolt-0.9.332}/jolt/scheduler.py +10 -15
- {jolt-0.9.328 → jolt-0.9.332}/jolt/tasks.py +21 -68
- {jolt-0.9.328 → jolt-0.9.332}/jolt/tools.py +29 -5
- jolt-0.9.332/jolt/version.py +1 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/PKG-INFO +10 -10
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/requires.txt +9 -9
- jolt-0.9.328/jolt/version.py +0 -1
- {jolt-0.9.328 → jolt-0.9.332}/README.rst +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/__init__.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/__main__.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/bin/fstree-darwin-x86_64 +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/bin/fstree-linux-x86_64 +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/chroot.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/cli.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/colors.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/common_pb2.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/common_pb2_grpc.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/config.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/error.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/expires.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/filesystem.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/hooks.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/influence.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/inspection.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/loader.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/manifest.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/options.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/pkgs/__init__.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/pkgs/golang.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/pkgs/nodejs.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/__init__.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/alias.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/allure.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/autoweight.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/cache.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/cmake.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/conan.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/cxx.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/cxxinfo.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/dashboard.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/debian.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/email.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/email.xslt +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/environ.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/gdb.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/gerrit.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/git.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/golang.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/googletest.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/http.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/junit.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/logstash.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/ninja-compdb.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/ninja.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/nodejs.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/paths.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/python.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/__init__.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/administration_pb2.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/administration_pb2_grpc.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/log_pb2.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/log_pb2_grpc.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/scheduler_pb2.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/scheduler_pb2_grpc.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/worker_pb2.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/remote_execution/worker_pb2_grpc.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/repo.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/report.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/strings.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/symlinks.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/telemetry.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/timeline.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/volume.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/yaml-ninja.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/plugins/yamltask.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/templates/cxxexecutable.cmake.template +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/templates/cxxlibrary.cmake.template +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/templates/export.sh.template +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/templates/timeline.html.template +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/timer.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/utils.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/version_utils.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt/xmldom.py +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/SOURCES.txt +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/dependency_links.txt +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/entry_points.txt +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/jolt.egg-info/top_level.txt +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/setup.cfg +0 -0
- {jolt-0.9.328 → jolt-0.9.332}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jolt
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.332
|
|
4
4
|
Summary: A task executor
|
|
5
5
|
Home-page: https://github.com/srand/jolt
|
|
6
6
|
Author: Robert Andersson
|
|
@@ -27,33 +27,33 @@ Requires-Dist: backports.tarfile==1.2.0
|
|
|
27
27
|
Requires-Dist: bz2file==0.98
|
|
28
28
|
Requires-Dist: certifi==2024.8.30
|
|
29
29
|
Requires-Dist: cffi==1.17.1
|
|
30
|
-
Requires-Dist: charset-normalizer==3.
|
|
30
|
+
Requires-Dist: charset-normalizer==3.4.0
|
|
31
31
|
Requires-Dist: click==8.1.7
|
|
32
32
|
Requires-Dist: colorama==0.4.6
|
|
33
33
|
Requires-Dist: fasteners==0.19
|
|
34
|
-
Requires-Dist: grpcio==1.
|
|
34
|
+
Requires-Dist: grpcio==1.68.0
|
|
35
35
|
Requires-Dist: idna==3.10
|
|
36
36
|
Requires-Dist: importlib-metadata==8.5.0
|
|
37
37
|
Requires-Dist: importlib_metadata
|
|
38
38
|
Requires-Dist: jaraco.classes==3.4.0
|
|
39
39
|
Requires-Dist: jaraco.context==6.0.1
|
|
40
|
-
Requires-Dist: jaraco.functools==4.0
|
|
40
|
+
Requires-Dist: jaraco.functools==4.1.0
|
|
41
41
|
Requires-Dist: jeepney==0.8.0
|
|
42
42
|
Requires-Dist: jinja2==3.1.4
|
|
43
|
-
Requires-Dist: keyring==25.
|
|
43
|
+
Requires-Dist: keyring==25.5.0
|
|
44
44
|
Requires-Dist: keyrings.alt==5.0.2
|
|
45
45
|
Requires-Dist: lxml==5.3.0
|
|
46
46
|
Requires-Dist: more-itertools==10.5.0
|
|
47
47
|
Requires-Dist: multi_key_dict
|
|
48
|
-
Requires-Dist: ninja==1.11.1.
|
|
49
|
-
Requires-Dist: protobuf==5.
|
|
50
|
-
Requires-Dist: psutil==6.
|
|
48
|
+
Requires-Dist: ninja==1.11.1.2
|
|
49
|
+
Requires-Dist: protobuf==5.29.0
|
|
50
|
+
Requires-Dist: psutil==6.1.0
|
|
51
51
|
Requires-Dist: pycparser==2.22
|
|
52
52
|
Requires-Dist: pygit2==1.15.1
|
|
53
53
|
Requires-Dist: requests==2.32.3
|
|
54
|
-
Requires-Dist: tqdm==4.
|
|
54
|
+
Requires-Dist: tqdm==4.67.1
|
|
55
55
|
Requires-Dist: urllib3==1.26.20
|
|
56
|
-
Requires-Dist: zipp==3.
|
|
56
|
+
Requires-Dist: zipp==3.21.0
|
|
57
57
|
Requires-Dist: zstandard==0.23.0
|
|
58
58
|
Provides-Extra: allure
|
|
59
59
|
Requires-Dist: allure-python-commons; extra == "allure"
|
|
@@ -879,6 +879,28 @@ class Artifact(object):
|
|
|
879
879
|
return self._node.task
|
|
880
880
|
|
|
881
881
|
|
|
882
|
+
class ArtifactToolsProxy(object):
|
|
883
|
+
def __init__(self, artifact, tools):
|
|
884
|
+
self._artifact = artifact
|
|
885
|
+
self._tools = tools
|
|
886
|
+
|
|
887
|
+
def __getattr__(self, name):
|
|
888
|
+
if name == "tools":
|
|
889
|
+
return self._tools
|
|
890
|
+
if name == "_artifact":
|
|
891
|
+
return self._artifact
|
|
892
|
+
attr = getattr(self._artifact.__class__, name, None)
|
|
893
|
+
if attr is not None and callable(attr):
|
|
894
|
+
return attr.__get__(self, ArtifactToolsProxy)
|
|
895
|
+
return getattr(self._artifact, name)
|
|
896
|
+
|
|
897
|
+
def __setattr__(self, name, value):
|
|
898
|
+
if name == "_artifact" or name == "_tools":
|
|
899
|
+
super(ArtifactToolsProxy, self).__setattr__(name, value)
|
|
900
|
+
else:
|
|
901
|
+
setattr(self._artifact, name, value)
|
|
902
|
+
|
|
903
|
+
|
|
882
904
|
class Context(object):
|
|
883
905
|
"""
|
|
884
906
|
Execution context and dependency wrapper.
|
|
@@ -904,18 +926,14 @@ class Context(object):
|
|
|
904
926
|
for dep in reversed(self._node.children):
|
|
905
927
|
for artifact in dep.artifacts:
|
|
906
928
|
# Create clone with tools from this task
|
|
907
|
-
artifact = self.
|
|
908
|
-
dep,
|
|
909
|
-
name=artifact.name,
|
|
910
|
-
session=artifact.is_session(),
|
|
911
|
-
tools=self._node.tools,
|
|
912
|
-
)
|
|
929
|
+
artifact = ArtifactToolsProxy(artifact, self._node.tools)
|
|
913
930
|
|
|
914
931
|
# Don't include session artifacts that don't exist,
|
|
915
932
|
# i.e. where no build has taken place due to presence
|
|
916
933
|
# of the persistent artifacts.
|
|
917
|
-
if
|
|
918
|
-
|
|
934
|
+
if not dep.is_resource():
|
|
935
|
+
if artifact.is_session() and not self._cache.is_available_locally(artifact):
|
|
936
|
+
continue
|
|
919
937
|
|
|
920
938
|
self._cache.unpack(artifact)
|
|
921
939
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from contextlib import contextmanager, ExitStack
|
|
1
|
+
from contextlib import contextmanager, ExitStack, nullcontext
|
|
2
2
|
import copy
|
|
3
3
|
import hashlib
|
|
4
4
|
from os import getenv
|
|
@@ -144,7 +144,11 @@ class TaskProxy(object):
|
|
|
144
144
|
|
|
145
145
|
@property
|
|
146
146
|
def instance(self):
|
|
147
|
-
return self.task.
|
|
147
|
+
return self.task.instance
|
|
148
|
+
|
|
149
|
+
@instance.setter
|
|
150
|
+
def instance(self, value):
|
|
151
|
+
self.task.instance = value
|
|
148
152
|
|
|
149
153
|
@property
|
|
150
154
|
def is_unstable(self):
|
|
@@ -183,7 +187,7 @@ class TaskProxy(object):
|
|
|
183
187
|
return len(self.ancestors) > 0
|
|
184
188
|
|
|
185
189
|
def has_artifact(self):
|
|
186
|
-
return self.is_cacheable() and not self.
|
|
190
|
+
return self.is_cacheable() and not self.is_alias()
|
|
187
191
|
|
|
188
192
|
def has_extensions(self):
|
|
189
193
|
return len(self.extensions) > 0
|
|
@@ -212,12 +216,14 @@ class TaskProxy(object):
|
|
|
212
216
|
def is_alias(self):
|
|
213
217
|
return isinstance(self.task, Alias)
|
|
214
218
|
|
|
215
|
-
def is_available_locally(self, extensions=True):
|
|
219
|
+
def is_available_locally(self, extensions=True, persistent_only=True):
|
|
216
220
|
dep_artifacts = []
|
|
217
221
|
if extensions:
|
|
218
222
|
for dep in self.extensions:
|
|
219
223
|
dep_artifacts += dep.artifacts
|
|
220
|
-
artifacts =
|
|
224
|
+
artifacts = self._artifacts + dep_artifacts
|
|
225
|
+
if persistent_only:
|
|
226
|
+
artifacts = filter(lambda a: not a.is_session(), artifacts)
|
|
221
227
|
return all(map(self.cache.is_available_locally, artifacts))
|
|
222
228
|
|
|
223
229
|
def is_available_remotely(self, extensions=True, cache=True):
|
|
@@ -250,6 +256,12 @@ class TaskProxy(object):
|
|
|
250
256
|
def is_goal(self, with_extensions=True):
|
|
251
257
|
return self._goal or (with_extensions and any([e.is_goal() for e in self.extensions]))
|
|
252
258
|
|
|
259
|
+
def is_local(self):
|
|
260
|
+
if self.is_extension():
|
|
261
|
+
return self.get_extended_task().is_local()
|
|
262
|
+
tasks = [self.task] + [e.task for e in self.extensions]
|
|
263
|
+
return any([task.local for task in tasks])
|
|
264
|
+
|
|
253
265
|
def in_progress(self):
|
|
254
266
|
return self._in_progress
|
|
255
267
|
|
|
@@ -316,7 +328,7 @@ class TaskProxy(object):
|
|
|
316
328
|
self._download = False
|
|
317
329
|
|
|
318
330
|
def download(self, force=False, session_only=False, persistent_only=False):
|
|
319
|
-
if not self.is_downloadable():
|
|
331
|
+
if not force and not self.is_downloadable():
|
|
320
332
|
return True
|
|
321
333
|
artifacts = self._artifacts
|
|
322
334
|
if session_only:
|
|
@@ -420,12 +432,13 @@ class TaskProxy(object):
|
|
|
420
432
|
self._finalized = True
|
|
421
433
|
self.identity
|
|
422
434
|
|
|
423
|
-
self._artifacts.extend(self.task._artifacts(self.cache, self))
|
|
424
|
-
|
|
425
435
|
hooks.task_created(self)
|
|
426
436
|
|
|
427
437
|
return self.identity
|
|
428
438
|
|
|
439
|
+
def finalize_artifacts(self):
|
|
440
|
+
self._artifacts.extend(self.task._artifacts(self.cache, self))
|
|
441
|
+
|
|
429
442
|
def taint(self, salt=None):
|
|
430
443
|
self.task.taint = self.task.taint or salt or uuid.uuid4()
|
|
431
444
|
if salt is None:
|
|
@@ -570,21 +583,20 @@ class TaskProxy(object):
|
|
|
570
583
|
for child in self.children:
|
|
571
584
|
if not child.has_artifact():
|
|
572
585
|
continue
|
|
586
|
+
|
|
587
|
+
if child.is_resource() and child.is_local() and child.options.worker:
|
|
588
|
+
return raise_task_error_if(
|
|
589
|
+
not child.download(force=True),
|
|
590
|
+
child, "Failed to download task artifact")
|
|
591
|
+
|
|
573
592
|
raise_task_error_if(
|
|
574
593
|
not child.is_completed() and child.is_unstable,
|
|
575
594
|
self, "Task depends on failed task '{}'", child.short_qualified_name)
|
|
576
595
|
if not child.is_available_locally(extensions=False):
|
|
577
596
|
raise_task_error_if(
|
|
578
|
-
not child.download(
|
|
597
|
+
not child.download(force=True),
|
|
579
598
|
child, "Failed to download task artifact")
|
|
580
599
|
|
|
581
|
-
def _run_prepare_resources(self, cache, force_upload=False, force_build=False):
|
|
582
|
-
from jolt.scheduler import ExecutorRegistry, JoltEnvironment
|
|
583
|
-
|
|
584
|
-
for child in filter(lambda task: task.is_resource() and not task.is_completed(), reversed(self.children)):
|
|
585
|
-
executor = ExecutorRegistry.get().create_local(child)
|
|
586
|
-
executor.run(JoltEnvironment(cache=cache))
|
|
587
|
-
|
|
588
600
|
def partially_available_locally(self):
|
|
589
601
|
availability = map(lambda a: self.cache.is_available_locally(a), self.artifacts)
|
|
590
602
|
return any(availability) and not all(availability)
|
|
@@ -607,16 +619,126 @@ class TaskProxy(object):
|
|
|
607
619
|
arch != platform_arch,
|
|
608
620
|
self, f"Task is not runnable on current platform (wants node.arch={arch})")
|
|
609
621
|
|
|
610
|
-
def
|
|
622
|
+
def run_acquire(self, artifact, owner, log_prefix=False):
|
|
623
|
+
"""
|
|
624
|
+
Acquires a resource and publishes its artifact.
|
|
625
|
+
|
|
626
|
+
The artifact is published to the cache even if the acquisition fails.
|
|
627
|
+
"""
|
|
628
|
+
try:
|
|
629
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
630
|
+
ts = utils.duration()
|
|
631
|
+
log.info(colors.blue("Resource acquisition started ({} for {})"), self.short_qualified_name, owner.short_qualified_name)
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
with log.thread_prefix(owner.identity[:8]) if log_prefix else nullcontext():
|
|
635
|
+
acquire = getattr(self.task, "acquire_" + artifact.name) if artifact.name != "main" else self.task.acquire
|
|
636
|
+
acquire(artifact, self.deps, self.tools, owner.task)
|
|
637
|
+
finally:
|
|
638
|
+
# Always commit the resource session artifact to the cache, even if the acquisition failed.
|
|
639
|
+
self.cache.commit(artifact)
|
|
640
|
+
|
|
641
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
642
|
+
log.info(colors.green("Resource acquisition finished after {} ({} for {})"), ts, self.short_qualified_name, owner.short_qualified_name)
|
|
643
|
+
|
|
644
|
+
except (KeyboardInterrupt, Exception) as e:
|
|
645
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
646
|
+
log.error(colors.red("Resource acquisition failed after {} ({} for {})"), ts, self.short_qualified_name, owner.short_qualified_name)
|
|
647
|
+
if self.task.release_on_error:
|
|
648
|
+
with utils.ignore_exception():
|
|
649
|
+
self.run_release(artifact, owner)
|
|
650
|
+
raise e
|
|
651
|
+
|
|
652
|
+
def run_release(self, artifact, owner, log_prefix=False):
|
|
653
|
+
"""
|
|
654
|
+
Releases a resource.
|
|
655
|
+
"""
|
|
656
|
+
try:
|
|
657
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
658
|
+
ts = utils.duration()
|
|
659
|
+
log.info(colors.blue("Resource release started ({} for {})"), self.short_qualified_name, owner.short_qualified_name)
|
|
660
|
+
|
|
661
|
+
with log.thread_prefix(owner.identity[:8]) if log_prefix else nullcontext():
|
|
662
|
+
release = getattr(self.task, "release_" + artifact.name) if artifact.name != "main" else self.task.release
|
|
663
|
+
release(artifact, self.deps, self.tools, owner.task)
|
|
664
|
+
|
|
665
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
666
|
+
log.info(colors.green("Resource release finished after {} ({} for {})"), ts, self.short_qualified_name, owner.short_qualified_name)
|
|
667
|
+
|
|
668
|
+
except (KeyboardInterrupt, Exception) as e:
|
|
669
|
+
if not isinstance(self.task, WorkspaceResource):
|
|
670
|
+
log.error(colors.red("Resource release failed after {} ({} for {})"), ts, self.short_qualified_name, owner.short_qualified_name)
|
|
671
|
+
raise e
|
|
672
|
+
|
|
673
|
+
@contextmanager
|
|
674
|
+
def run_resources(self):
|
|
675
|
+
"""
|
|
676
|
+
Acquires and releases resources for the task.
|
|
677
|
+
|
|
678
|
+
The method is called by executors before invoking the task proxy's run() method.
|
|
679
|
+
Resource dependencies are acquired and released in reverse order. If an acquisition fails,
|
|
680
|
+
already acquired resources are released in reverse order and the exception is propagated
|
|
681
|
+
to the caller.
|
|
682
|
+
|
|
683
|
+
Resource artifacts are always published and uploaded if the acquisition has been started,
|
|
684
|
+
even if the acquisition fails. That way, a failed acquisition can be debugged.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
# Log messages are prefixed with task identity if resources are acquired in parallel
|
|
688
|
+
log_prefix = False
|
|
689
|
+
|
|
690
|
+
# Collect list of resource dependencies
|
|
691
|
+
resource_deps = [child for child in self.children if child.is_resource()]
|
|
692
|
+
|
|
693
|
+
if self.options.worker:
|
|
694
|
+
# Exclude local resources when running as worker. They are already acquired by the client.
|
|
695
|
+
resource_deps = [child for child in resource_deps if not child.is_local()]
|
|
696
|
+
elif self.options.network and not self.is_local():
|
|
697
|
+
# Exclude non-local resources in the client when running a network build.
|
|
698
|
+
# They are acquired by the remote worker.
|
|
699
|
+
resource_deps = [child for child in resource_deps if child.is_local()]
|
|
700
|
+
log_prefix = True
|
|
701
|
+
|
|
702
|
+
exitstack = ExitStack()
|
|
703
|
+
acquired = []
|
|
704
|
+
try:
|
|
705
|
+
# Acquire resource dependencies in reverse order.
|
|
706
|
+
for resource in reversed(resource_deps):
|
|
707
|
+
# Download resource dependencies if not already done.
|
|
708
|
+
resource._run_download_dependencies(self.cache, force_upload=False, force_build=False)
|
|
709
|
+
|
|
710
|
+
with resource.lock_artifacts(discard=False):
|
|
711
|
+
resource.deps = self.cache.get_context(resource)
|
|
712
|
+
exitstack.enter_context(resource.deps)
|
|
713
|
+
|
|
714
|
+
# Just like tasks, a resource may have multiple artifacts. Run acquire for each artifact.
|
|
715
|
+
for artifact in resource.artifacts:
|
|
716
|
+
try:
|
|
717
|
+
resource.run_acquire(artifact, self, log_prefix=log_prefix)
|
|
718
|
+
acquired.append(resource)
|
|
719
|
+
finally:
|
|
720
|
+
# Always upload the artifact session artifact to the cache, even if the acquisition failed.
|
|
721
|
+
if not resource.is_workspace_resource():
|
|
722
|
+
resource.upload(locked=False, session_only=True, artifacts=[artifact])
|
|
723
|
+
|
|
724
|
+
yield
|
|
725
|
+
|
|
726
|
+
finally:
|
|
727
|
+
for resource in reversed(acquired):
|
|
728
|
+
for artifact in resource.artifacts:
|
|
729
|
+
resource.run_release(artifact, self, log_prefix=log_prefix)
|
|
730
|
+
exitstack.close()
|
|
731
|
+
|
|
732
|
+
def run(self, env, force_upload=False, force_build=False):
|
|
733
|
+
cache = env.cache
|
|
734
|
+
queue = env.queue
|
|
735
|
+
|
|
611
736
|
with self.tools:
|
|
612
737
|
available_locally = available_remotely = False
|
|
613
738
|
|
|
614
739
|
# Download dependency artifacts if not already done
|
|
615
740
|
self._run_download_dependencies(cache, force_upload, force_build)
|
|
616
741
|
|
|
617
|
-
# Prepare resources if not already done. They are not acquired yet.
|
|
618
|
-
self._run_prepare_resources(cache, force_upload, force_build)
|
|
619
|
-
|
|
620
742
|
# Check if task artifact is available locally or remotely,
|
|
621
743
|
# either skip execution or download it if necessary.
|
|
622
744
|
if not force_build:
|
|
@@ -689,10 +811,14 @@ class TaskProxy(object):
|
|
|
689
811
|
raise e
|
|
690
812
|
|
|
691
813
|
except Exception as e:
|
|
692
|
-
self.failed_execution()
|
|
814
|
+
self.failed_execution(interrupt=queue.is_aborted() if queue else False)
|
|
815
|
+
|
|
693
816
|
with utils.ignore_exception():
|
|
694
817
|
exitstack.close()
|
|
695
818
|
|
|
819
|
+
if queue is not None and queue.is_aborted():
|
|
820
|
+
raise KeyboardInterrupt()
|
|
821
|
+
|
|
696
822
|
if cli.debug_enabled:
|
|
697
823
|
import pdb
|
|
698
824
|
extype, value, tb = sys.exc_info()
|
|
@@ -734,7 +860,7 @@ class TaskProxy(object):
|
|
|
734
860
|
|
|
735
861
|
for extension in self.extensions:
|
|
736
862
|
with hooks.task_run(extension):
|
|
737
|
-
extension.run(
|
|
863
|
+
extension.run(env, force_upload, force_build)
|
|
738
864
|
|
|
739
865
|
def publish(self, context, artifact, buildlog=None):
|
|
740
866
|
hooks.task_prepublish(self, artifact, self.tools)
|
|
@@ -929,7 +1055,7 @@ class GraphBuilder(object):
|
|
|
929
1055
|
self.progress = progress
|
|
930
1056
|
self.options = options or JoltOptions()
|
|
931
1057
|
|
|
932
|
-
def _get_node(self, progress, name):
|
|
1058
|
+
def _get_node(self, progress, name, parent=None):
|
|
933
1059
|
name = utils.stable_task_name(name)
|
|
934
1060
|
node = self.nodes.get(name)
|
|
935
1061
|
if not node:
|
|
@@ -938,8 +1064,12 @@ class GraphBuilder(object):
|
|
|
938
1064
|
if node is not None:
|
|
939
1065
|
return node
|
|
940
1066
|
node = TaskProxy(task, self.graph, self.cache, self.options)
|
|
941
|
-
|
|
942
|
-
|
|
1067
|
+
if not node.is_resource():
|
|
1068
|
+
self.nodes[node.short_qualified_name] = node
|
|
1069
|
+
self.nodes[node.qualified_name] = node
|
|
1070
|
+
elif parent:
|
|
1071
|
+
# A resource inherits its instance uuid from the consuming task
|
|
1072
|
+
node.instance = parent.instance
|
|
943
1073
|
if self.options.salt:
|
|
944
1074
|
node.taint(self.options.salt)
|
|
945
1075
|
self._build_node(progress, node)
|
|
@@ -950,7 +1080,7 @@ class GraphBuilder(object):
|
|
|
950
1080
|
self.graph.add_node(node)
|
|
951
1081
|
|
|
952
1082
|
if node.task.extends:
|
|
953
|
-
extended_node = self._get_node(progress, node.task.extends)
|
|
1083
|
+
extended_node = self._get_node(progress, node.task.extends, parent=node)
|
|
954
1084
|
self.graph.add_edges_from([(node, extended_node)])
|
|
955
1085
|
node.set_extended_task(extended_node)
|
|
956
1086
|
extended_node.add_extension(node)
|
|
@@ -961,7 +1091,8 @@ class GraphBuilder(object):
|
|
|
961
1091
|
|
|
962
1092
|
for requirement in node.task.requires:
|
|
963
1093
|
alias, artifact, task, name = utils.parse_aliased_task_name(requirement)
|
|
964
|
-
child = self._get_node(progress, utils.format_task_name(task, name))
|
|
1094
|
+
child = self._get_node(progress, utils.format_task_name(task, name), parent=node)
|
|
1095
|
+
|
|
965
1096
|
# Create direct edges from alias parents to alias children
|
|
966
1097
|
if child.is_alias():
|
|
967
1098
|
for child_child in child.children:
|
|
@@ -997,6 +1128,11 @@ class GraphBuilder(object):
|
|
|
997
1128
|
node.finalize(self.graph, self.manifest)
|
|
998
1129
|
p.update(1)
|
|
999
1130
|
|
|
1131
|
+
# Create artifacts in forward order so that parent identities are available
|
|
1132
|
+
# when creating resource artifacts that depend on them.
|
|
1133
|
+
for node in topological_nodes:
|
|
1134
|
+
node.finalize_artifacts()
|
|
1135
|
+
|
|
1000
1136
|
max_time = 0
|
|
1001
1137
|
min_time = 0
|
|
1002
1138
|
for node in topological_nodes:
|
|
@@ -143,6 +143,25 @@ class TqdmStream(object):
|
|
|
143
143
|
getattr(self.stream, 'flush', lambda: None)()
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
class ThreadPrefix(logging.LoggerAdapter):
|
|
147
|
+
thread_prefix = {}
|
|
148
|
+
|
|
149
|
+
def process(self, msg, kwargs):
|
|
150
|
+
tid = threading.current_thread()
|
|
151
|
+
if tid in self.thread_prefix:
|
|
152
|
+
msg = f"[{self.thread_prefix[tid]}] {msg}"
|
|
153
|
+
return msg, kwargs
|
|
154
|
+
|
|
155
|
+
def set_thread_prefix(self, prefix):
|
|
156
|
+
tid = threading.current_thread()
|
|
157
|
+
self.thread_prefix[tid] = prefix
|
|
158
|
+
|
|
159
|
+
def clear_thread_prefix(self):
|
|
160
|
+
tid = threading.current_thread()
|
|
161
|
+
if tid in self.thread_prefix:
|
|
162
|
+
del self.thread_prefix[tid]
|
|
163
|
+
|
|
164
|
+
|
|
146
165
|
# silence root logger
|
|
147
166
|
_root = logging.getLogger()
|
|
148
167
|
_root.setLevel(logging.CRITICAL)
|
|
@@ -175,6 +194,7 @@ _stderr.addFilter(Filter(lambda r: r.levelno >= ERROR or r.levelno == EXCEPTION)
|
|
|
175
194
|
|
|
176
195
|
_logger.addHandler(_stdout)
|
|
177
196
|
_logger.addHandler(_stderr)
|
|
197
|
+
_logger_frontend = ThreadPrefix(_logger, {})
|
|
178
198
|
|
|
179
199
|
_file_formatter = Formatter('{asctime} [{levelname:>7}] {message}')
|
|
180
200
|
|
|
@@ -212,35 +232,35 @@ def log(level, message, created=None, context=None, prefix=False):
|
|
|
212
232
|
|
|
213
233
|
|
|
214
234
|
def info(fmt, *args, **kwargs):
|
|
215
|
-
|
|
235
|
+
_logger_frontend.log(INFO, fmt, *args, **kwargs)
|
|
216
236
|
|
|
217
237
|
|
|
218
238
|
def warning(fmt, *args, **kwargs):
|
|
219
|
-
|
|
239
|
+
_logger_frontend.log(WARNING, fmt, *args, **kwargs)
|
|
220
240
|
|
|
221
241
|
|
|
222
242
|
def verbose(fmt, *args, **kwargs):
|
|
223
|
-
|
|
243
|
+
_logger_frontend.log(VERBOSE, fmt, *args, **kwargs)
|
|
224
244
|
|
|
225
245
|
|
|
226
246
|
def debug(fmt, *args, **kwargs):
|
|
227
|
-
|
|
247
|
+
_logger_frontend.log(DEBUG, fmt, *args, **kwargs)
|
|
228
248
|
|
|
229
249
|
|
|
230
250
|
def error(fmt, *args, **kwargs):
|
|
231
|
-
|
|
251
|
+
_logger_frontend.log(ERROR, fmt, *args, **kwargs)
|
|
232
252
|
|
|
233
253
|
|
|
234
254
|
def stdout(line, **kwargs):
|
|
235
255
|
line = line.replace("{", "{{")
|
|
236
256
|
line = line.replace("}", "}}")
|
|
237
|
-
|
|
257
|
+
_logger_frontend.log(STDOUT, line, extra=kwargs)
|
|
238
258
|
|
|
239
259
|
|
|
240
260
|
def stderr(line, **kwargs):
|
|
241
261
|
line = line.replace("{", "{{")
|
|
242
262
|
line = line.replace("}", "}}")
|
|
243
|
-
|
|
263
|
+
_logger_frontend.log(STDERR, line, extra=kwargs)
|
|
244
264
|
|
|
245
265
|
|
|
246
266
|
def format_exception_msg(exc):
|
|
@@ -273,7 +293,7 @@ def format_exception_msg(exc):
|
|
|
273
293
|
def exception(exc=None, error=True):
|
|
274
294
|
if exc:
|
|
275
295
|
if error:
|
|
276
|
-
|
|
296
|
+
_logger_frontend.log(ERROR, format_exception_msg(exc))
|
|
277
297
|
|
|
278
298
|
tb = traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
|
|
279
299
|
installdir = fs.path.dirname(__file__)
|
|
@@ -288,7 +308,7 @@ def exception(exc=None, error=True):
|
|
|
288
308
|
line = line.replace("{", "{{")
|
|
289
309
|
line = line.replace("}", "}}")
|
|
290
310
|
line = line.strip()
|
|
291
|
-
|
|
311
|
+
_logger_frontend.log(EXCEPTION, line)
|
|
292
312
|
|
|
293
313
|
|
|
294
314
|
def transfer(line, context):
|
|
@@ -308,7 +328,7 @@ def transfer(line, context):
|
|
|
308
328
|
elif line.startswith("[ EXCEPT]"):
|
|
309
329
|
outline1 = outline1.replace("{", "{{")
|
|
310
330
|
outline1 = outline1.replace("}", "}}")
|
|
311
|
-
|
|
331
|
+
_logger_frontend.log(EXCEPTION, outline1)
|
|
312
332
|
elif line.startswith("[ STDERR]"):
|
|
313
333
|
stderr(outline1, prefix=True)
|
|
314
334
|
elif line.startswith("[ STDOUT]"):
|
|
@@ -491,6 +511,15 @@ def map_thread(thread_from, thread_to):
|
|
|
491
511
|
_thread_map.unmap(tid)
|
|
492
512
|
|
|
493
513
|
|
|
514
|
+
@contextmanager
|
|
515
|
+
def thread_prefix(prefix):
|
|
516
|
+
try:
|
|
517
|
+
_logger_frontend.set_thread_prefix(prefix)
|
|
518
|
+
yield
|
|
519
|
+
finally:
|
|
520
|
+
_logger_frontend.clear_thread_prefix()
|
|
521
|
+
|
|
522
|
+
|
|
494
523
|
class _LogStream(object):
|
|
495
524
|
def __init__(self):
|
|
496
525
|
self.buf = ""
|
|
@@ -25,8 +25,6 @@ class DockerListVariable(ArtifactListAttribute):
|
|
|
25
25
|
|
|
26
26
|
class DockerLoadListVariable(DockerListVariable):
|
|
27
27
|
def apply(self, task, artifact):
|
|
28
|
-
if isinstance(task, Resource):
|
|
29
|
-
return
|
|
30
28
|
for image in self.items():
|
|
31
29
|
task.tools.run(
|
|
32
30
|
"docker load -i {}",
|
|
@@ -35,16 +33,12 @@ class DockerLoadListVariable(DockerListVariable):
|
|
|
35
33
|
|
|
36
34
|
class DockerPullListVariable(DockerListVariable):
|
|
37
35
|
def apply(self, task, artifact):
|
|
38
|
-
if isinstance(task, Resource):
|
|
39
|
-
return
|
|
40
36
|
for image in self.items():
|
|
41
37
|
task.tools.run("docker pull {}", image, output_on_error=True)
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
class DockerRmiListVariable(DockerListVariable):
|
|
45
41
|
def unapply(self, task, artifact):
|
|
46
|
-
if isinstance(task, Resource):
|
|
47
|
-
return
|
|
48
42
|
for image in self.items():
|
|
49
43
|
task.tools.run("docker rmi -f {}", image, output_on_error=True)
|
|
50
44
|
|
|
@@ -499,6 +493,9 @@ class DockerImage(Task):
|
|
|
499
493
|
"""
|
|
500
494
|
abstract = True
|
|
501
495
|
|
|
496
|
+
annotations = []
|
|
497
|
+
""" A list of image annotations """
|
|
498
|
+
|
|
502
499
|
autoload = True
|
|
503
500
|
"""
|
|
504
501
|
Automatically load image file into local registry when the artifact is
|
|
@@ -585,6 +582,10 @@ class DockerImage(Task):
|
|
|
585
582
|
with _Tarfile.open(layerpath, 'r') as tar:
|
|
586
583
|
tar.extractall(targetpath)
|
|
587
584
|
|
|
585
|
+
@property
|
|
586
|
+
def _annotations(self):
|
|
587
|
+
return " ".join([utils.option("--annotation ", self.tools.expand(an)) for an in self.annotations])
|
|
588
|
+
|
|
588
589
|
@property
|
|
589
590
|
def _buildargs(self):
|
|
590
591
|
return " ".join([utils.option("--build-arg ", self.tools.expand(ba)) for ba in self.buildargs])
|
|
@@ -626,7 +627,7 @@ class DockerImage(Task):
|
|
|
626
627
|
tools.expand_relpath(context))
|
|
627
628
|
|
|
628
629
|
with tools.cwd(context):
|
|
629
|
-
tools.run("docker build {_platform} . -f {} {_buildargs} {_labels} {_tags} {pull}{squash}",
|
|
630
|
+
tools.run("docker build {_platform} . -f {} {_annotations} {_buildargs} {_labels} {_tags} {pull}{squash}",
|
|
630
631
|
utils.quote(dockerfile), pull=pull, squash=squash)
|
|
631
632
|
|
|
632
633
|
try:
|