jolt 0.9.123__py3-none-any.whl → 0.9.435__py3-none-any.whl
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/__init__.py +80 -7
- jolt/__main__.py +9 -1
- jolt/bin/fstree-darwin-x86_64 +0 -0
- jolt/bin/fstree-linux-x86_64 +0 -0
- jolt/cache.py +832 -362
- jolt/chroot.py +156 -0
- jolt/cli.py +281 -162
- jolt/common_pb2.py +63 -0
- jolt/common_pb2_grpc.py +4 -0
- jolt/config.py +98 -41
- jolt/error.py +19 -4
- jolt/filesystem.py +2 -6
- jolt/graph.py +705 -117
- jolt/hooks.py +43 -0
- jolt/influence.py +122 -3
- jolt/loader.py +369 -121
- jolt/log.py +225 -63
- jolt/manifest.py +28 -38
- jolt/options.py +35 -10
- jolt/pkgs/abseil.py +42 -0
- jolt/pkgs/asio.py +25 -0
- jolt/pkgs/autoconf.py +41 -0
- jolt/pkgs/automake.py +41 -0
- jolt/pkgs/b2.py +31 -0
- jolt/pkgs/boost.py +111 -0
- jolt/pkgs/boringssl.py +32 -0
- jolt/pkgs/busybox.py +39 -0
- jolt/pkgs/bzip2.py +43 -0
- jolt/pkgs/cares.py +29 -0
- jolt/pkgs/catch2.py +36 -0
- jolt/pkgs/cbindgen.py +17 -0
- jolt/pkgs/cista.py +19 -0
- jolt/pkgs/clang.py +44 -0
- jolt/pkgs/cli11.py +24 -0
- jolt/pkgs/cmake.py +48 -0
- jolt/pkgs/cpython.py +196 -0
- jolt/pkgs/crun.py +29 -0
- jolt/pkgs/curl.py +38 -0
- jolt/pkgs/dbus.py +18 -0
- jolt/pkgs/double_conversion.py +24 -0
- jolt/pkgs/fastfloat.py +21 -0
- jolt/pkgs/ffmpeg.py +28 -0
- jolt/pkgs/flatbuffers.py +29 -0
- jolt/pkgs/fmt.py +27 -0
- jolt/pkgs/fstree.py +20 -0
- jolt/pkgs/gflags.py +18 -0
- jolt/pkgs/glib.py +18 -0
- jolt/pkgs/glog.py +25 -0
- jolt/pkgs/glslang.py +21 -0
- jolt/pkgs/golang.py +16 -11
- jolt/pkgs/googlebenchmark.py +18 -0
- jolt/pkgs/googletest.py +46 -0
- jolt/pkgs/gperf.py +15 -0
- jolt/pkgs/grpc.py +73 -0
- jolt/pkgs/hdf5.py +19 -0
- jolt/pkgs/help2man.py +14 -0
- jolt/pkgs/inja.py +28 -0
- jolt/pkgs/jsoncpp.py +31 -0
- jolt/pkgs/libarchive.py +43 -0
- jolt/pkgs/libcap.py +44 -0
- jolt/pkgs/libdrm.py +44 -0
- jolt/pkgs/libedit.py +42 -0
- jolt/pkgs/libevent.py +31 -0
- jolt/pkgs/libexpat.py +27 -0
- jolt/pkgs/libfastjson.py +21 -0
- jolt/pkgs/libffi.py +16 -0
- jolt/pkgs/libglvnd.py +30 -0
- jolt/pkgs/libogg.py +28 -0
- jolt/pkgs/libpciaccess.py +18 -0
- jolt/pkgs/libseccomp.py +21 -0
- jolt/pkgs/libtirpc.py +24 -0
- jolt/pkgs/libtool.py +42 -0
- jolt/pkgs/libunwind.py +35 -0
- jolt/pkgs/libva.py +18 -0
- jolt/pkgs/libvorbis.py +33 -0
- jolt/pkgs/libxml2.py +35 -0
- jolt/pkgs/libxslt.py +17 -0
- jolt/pkgs/libyajl.py +16 -0
- jolt/pkgs/llvm.py +81 -0
- jolt/pkgs/lua.py +54 -0
- jolt/pkgs/lz4.py +26 -0
- jolt/pkgs/m4.py +14 -0
- jolt/pkgs/make.py +17 -0
- jolt/pkgs/mesa.py +81 -0
- jolt/pkgs/meson.py +17 -0
- jolt/pkgs/mstch.py +28 -0
- jolt/pkgs/mysql.py +60 -0
- jolt/pkgs/nasm.py +49 -0
- jolt/pkgs/ncurses.py +30 -0
- jolt/pkgs/ng_log.py +25 -0
- jolt/pkgs/ninja.py +45 -0
- jolt/pkgs/nlohmann_json.py +25 -0
- jolt/pkgs/nodejs.py +19 -11
- jolt/pkgs/opencv.py +24 -0
- jolt/pkgs/openjdk.py +26 -0
- jolt/pkgs/openssl.py +103 -0
- jolt/pkgs/paho.py +76 -0
- jolt/pkgs/patchelf.py +16 -0
- jolt/pkgs/perl.py +42 -0
- jolt/pkgs/pkgconfig.py +64 -0
- jolt/pkgs/poco.py +39 -0
- jolt/pkgs/protobuf.py +77 -0
- jolt/pkgs/pugixml.py +27 -0
- jolt/pkgs/python.py +19 -0
- jolt/pkgs/qt.py +35 -0
- jolt/pkgs/rapidjson.py +26 -0
- jolt/pkgs/rapidyaml.py +28 -0
- jolt/pkgs/re2.py +30 -0
- jolt/pkgs/re2c.py +17 -0
- jolt/pkgs/readline.py +15 -0
- jolt/pkgs/rust.py +41 -0
- jolt/pkgs/sdl.py +28 -0
- jolt/pkgs/simdjson.py +27 -0
- jolt/pkgs/soci.py +46 -0
- jolt/pkgs/spdlog.py +29 -0
- jolt/pkgs/spirv_llvm.py +21 -0
- jolt/pkgs/spirv_tools.py +24 -0
- jolt/pkgs/sqlite.py +83 -0
- jolt/pkgs/ssl.py +12 -0
- jolt/pkgs/texinfo.py +15 -0
- jolt/pkgs/tomlplusplus.py +22 -0
- jolt/pkgs/wayland.py +26 -0
- jolt/pkgs/x11.py +58 -0
- jolt/pkgs/xerces_c.py +20 -0
- jolt/pkgs/xorg.py +360 -0
- jolt/pkgs/xz.py +29 -0
- jolt/pkgs/yamlcpp.py +30 -0
- jolt/pkgs/zeromq.py +47 -0
- jolt/pkgs/zlib.py +87 -0
- jolt/pkgs/zstd.py +33 -0
- jolt/plugins/alias.py +3 -0
- jolt/plugins/allure.py +5 -2
- jolt/plugins/autotools.py +66 -0
- jolt/plugins/cache.py +133 -0
- jolt/plugins/cmake.py +74 -6
- jolt/plugins/conan.py +238 -0
- jolt/plugins/cxx.py +698 -0
- jolt/plugins/cxxinfo.py +7 -0
- jolt/plugins/dashboard.py +1 -1
- jolt/plugins/docker.py +80 -23
- jolt/plugins/email.py +2 -2
- jolt/plugins/email.xslt +144 -101
- jolt/plugins/environ.py +11 -0
- jolt/plugins/fetch.py +141 -0
- jolt/plugins/gdb.py +39 -19
- jolt/plugins/gerrit.py +1 -14
- jolt/plugins/git.py +283 -85
- jolt/plugins/googletest.py +2 -1
- jolt/plugins/http.py +36 -38
- jolt/plugins/libtool.py +63 -0
- jolt/plugins/linux.py +990 -0
- jolt/plugins/logstash.py +4 -4
- jolt/plugins/meson.py +61 -0
- jolt/plugins/ninja-compdb.py +99 -30
- jolt/plugins/ninja.py +468 -166
- jolt/plugins/paths.py +11 -1
- jolt/plugins/pkgconfig.py +219 -0
- jolt/plugins/podman.py +136 -92
- jolt/plugins/python.py +137 -0
- jolt/plugins/remote_execution/__init__.py +0 -0
- jolt/plugins/remote_execution/administration_pb2.py +46 -0
- jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
- jolt/plugins/remote_execution/log_pb2.py +32 -0
- jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
- jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
- jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
- jolt/plugins/remote_execution/worker_pb2.py +38 -0
- jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
- jolt/plugins/report.py +12 -2
- jolt/plugins/rust.py +25 -0
- jolt/plugins/scheduler.py +710 -0
- jolt/plugins/selfdeploy/setup.py +8 -4
- jolt/plugins/selfdeploy.py +138 -88
- jolt/plugins/strings.py +35 -22
- jolt/plugins/symlinks.py +26 -11
- jolt/plugins/telemetry.py +5 -2
- jolt/plugins/timeline.py +13 -3
- jolt/plugins/volume.py +46 -48
- jolt/scheduler.py +589 -192
- jolt/tasks.py +625 -121
- jolt/templates/timeline.html.template +44 -47
- jolt/timer.py +22 -0
- jolt/tools.py +638 -282
- jolt/utils.py +211 -7
- jolt/version.py +1 -1
- jolt/xmldom.py +12 -2
- {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/METADATA +97 -38
- jolt-0.9.435.dist-info/RECORD +207 -0
- {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/WHEEL +1 -1
- jolt/plugins/amqp.py +0 -834
- jolt/plugins/debian.py +0 -338
- jolt/plugins/ftp.py +0 -181
- jolt/plugins/repo.py +0 -253
- jolt-0.9.123.dist-info/RECORD +0 -77
- {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/entry_points.txt +0 -0
- {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/top_level.txt +0 -0
jolt/tasks.py
CHANGED
|
@@ -7,10 +7,13 @@ import fnmatch
|
|
|
7
7
|
import functools
|
|
8
8
|
import hashlib
|
|
9
9
|
import platform
|
|
10
|
+
from threading import RLock
|
|
10
11
|
import subprocess
|
|
11
12
|
from os import environ
|
|
13
|
+
from os import sys as os_sys
|
|
12
14
|
import sys
|
|
13
15
|
import unittest as ut
|
|
16
|
+
from urllib.parse import urlparse
|
|
14
17
|
import uuid
|
|
15
18
|
import re
|
|
16
19
|
import traceback
|
|
@@ -21,10 +24,11 @@ from jolt import utils
|
|
|
21
24
|
from jolt.cache import ArtifactAttributeSetProvider
|
|
22
25
|
from jolt.error import raise_error_if, raise_task_error, raise_task_error_if
|
|
23
26
|
from jolt.error import raise_unreported_task_error_if
|
|
24
|
-
from jolt.error import JoltError, JoltCommandError
|
|
27
|
+
from jolt.error import JoltError, JoltCommandError, LoggedJoltError
|
|
25
28
|
from jolt.expires import Immediately
|
|
26
29
|
from jolt.influence import FileInfluence, TaintInfluenceProvider
|
|
27
30
|
from jolt.influence import TaskClassSourceInfluence
|
|
31
|
+
from jolt.influence import CallbackInfluence
|
|
28
32
|
from jolt.influence import attribute as attribute_influence
|
|
29
33
|
from jolt.influence import environ as environ_influence
|
|
30
34
|
from jolt.influence import source as source_influence
|
|
@@ -133,7 +137,7 @@ class Parameter(object):
|
|
|
133
137
|
self.name = name
|
|
134
138
|
|
|
135
139
|
def __init__(self, default=None, values=None, required=True,
|
|
136
|
-
const=False, influence=True, help=None):
|
|
140
|
+
const=False, influence=True, help=None, valuesfn=None):
|
|
137
141
|
"""
|
|
138
142
|
Creates a new parameter.
|
|
139
143
|
|
|
@@ -141,6 +145,10 @@ class Parameter(object):
|
|
|
141
145
|
default (str, optional): An optional default value.
|
|
142
146
|
values (list, optional): A list of accepted values. An
|
|
143
147
|
assertion is raised if an unlisted value is assigned to the parameter.
|
|
148
|
+
valuesfn (func, optional); A function that validates the assigned value.
|
|
149
|
+
If both values and valuesfn are specified, the values list is validated
|
|
150
|
+
first. The function is passed the assigned value and should return.
|
|
151
|
+
boolean value. If the function returns False, a ParameterValueError is raised.
|
|
144
152
|
required (boolean, optional): If required, the parameter must be assigned
|
|
145
153
|
a value before the task can be executed. The default is ``True``.
|
|
146
154
|
const (boolean, optional): If const is True, the parameter is immutable
|
|
@@ -162,6 +170,7 @@ class Parameter(object):
|
|
|
162
170
|
self._default = default
|
|
163
171
|
self._value = default
|
|
164
172
|
self._accepted_values = values
|
|
173
|
+
self._accepted_values_fn = valuesfn
|
|
165
174
|
self._required = required
|
|
166
175
|
self._const = const
|
|
167
176
|
self._influence = influence
|
|
@@ -188,7 +197,7 @@ class Parameter(object):
|
|
|
188
197
|
def highlight(value):
|
|
189
198
|
return colors.bright(value) if self._is_default(value) else colors.dim(value)
|
|
190
199
|
|
|
191
|
-
return "[{}]".format(", ".join([highlight(value) for value in accepted])) if accepted else ""
|
|
200
|
+
return "[{}]".format(", ".join([highlight(str(value)) for value in accepted])) if accepted else ""
|
|
192
201
|
|
|
193
202
|
def __str__(self):
|
|
194
203
|
""" Returns the parameter value as a string """
|
|
@@ -197,6 +206,8 @@ class Parameter(object):
|
|
|
197
206
|
def _validate(self, value, what=None):
|
|
198
207
|
if self._accepted_values is not None and value not in self._accepted_values:
|
|
199
208
|
raise ParameterValueError(self, value, what=what)
|
|
209
|
+
if self._accepted_values_fn is not None and not self._accepted_values_fn(value):
|
|
210
|
+
raise ParameterValueError(self, value, what=what)
|
|
200
211
|
|
|
201
212
|
def get_default(self):
|
|
202
213
|
""" Get the default value of the parameter.
|
|
@@ -350,7 +361,7 @@ class BooleanParameter(Parameter):
|
|
|
350
361
|
|
|
351
362
|
"""
|
|
352
363
|
default = str(default).lower() if default is not None else None
|
|
353
|
-
super(
|
|
364
|
+
super().__init__(
|
|
354
365
|
default,
|
|
355
366
|
values=["false", "true", "0", "1", "no", "yes"],
|
|
356
367
|
required=required,
|
|
@@ -372,7 +383,7 @@ class BooleanParameter(Parameter):
|
|
|
372
383
|
ParameterValueError: If the parameter is assigned an illegal value.
|
|
373
384
|
"""
|
|
374
385
|
value = str(value).lower()
|
|
375
|
-
super(
|
|
386
|
+
super().set_value(value)
|
|
376
387
|
|
|
377
388
|
@property
|
|
378
389
|
def is_true(self):
|
|
@@ -434,7 +445,7 @@ class IntParameter(Parameter):
|
|
|
434
445
|
"""
|
|
435
446
|
|
|
436
447
|
def __init__(self, default=None, min=None, max=None, values=None, required=True, const=False,
|
|
437
|
-
influence=True, help=None):
|
|
448
|
+
influence=True, help=None, valuesfn=None):
|
|
438
449
|
"""
|
|
439
450
|
Creates a new parameter.
|
|
440
451
|
|
|
@@ -482,7 +493,8 @@ class IntParameter(Parameter):
|
|
|
482
493
|
required=required,
|
|
483
494
|
const=const,
|
|
484
495
|
influence=influence,
|
|
485
|
-
help=help
|
|
496
|
+
help=help,
|
|
497
|
+
valuesfn=valuesfn)
|
|
486
498
|
|
|
487
499
|
def _validate(self, value, what=None):
|
|
488
500
|
if self._min is not None and value < self._min:
|
|
@@ -686,6 +698,10 @@ class ListParameter(Parameter):
|
|
|
686
698
|
for item in value:
|
|
687
699
|
if item not in self._accepted_values:
|
|
688
700
|
raise ParameterValueError(self, item, what=what)
|
|
701
|
+
if self._accepted_values_fn is not None:
|
|
702
|
+
for item in value:
|
|
703
|
+
if not self._accepted_values_fn(item):
|
|
704
|
+
raise ParameterValueError(self, item, what=what)
|
|
689
705
|
|
|
690
706
|
def get_value(self):
|
|
691
707
|
return "+".join(self._value)
|
|
@@ -767,6 +783,7 @@ class TaskRegistry(object):
|
|
|
767
783
|
self.env = env
|
|
768
784
|
self.tasks = {}
|
|
769
785
|
self.instances = {}
|
|
786
|
+
self._workspace_resources = []
|
|
770
787
|
|
|
771
788
|
@staticmethod
|
|
772
789
|
def get(*args, **kwargs):
|
|
@@ -775,6 +792,21 @@ class TaskRegistry(object):
|
|
|
775
792
|
return TaskRegistry._instance
|
|
776
793
|
|
|
777
794
|
def add_task_class(self, cls):
|
|
795
|
+
"""
|
|
796
|
+
Add a task class to the registry.
|
|
797
|
+
|
|
798
|
+
The class is decorated to require workspace resources.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
registry = self
|
|
802
|
+
|
|
803
|
+
def _workspace_resources(self):
|
|
804
|
+
return registry._workspace_resources
|
|
805
|
+
|
|
806
|
+
if not issubclass(cls, WorkspaceResource):
|
|
807
|
+
cls = attributes.requires("_workspace_resources")(cls)
|
|
808
|
+
cls._workspace_resources = property(_workspace_resources)
|
|
809
|
+
|
|
778
810
|
self.tasks[cls.name] = cls
|
|
779
811
|
|
|
780
812
|
def add_task(self, task, extra_params):
|
|
@@ -783,13 +815,20 @@ class TaskRegistry(object):
|
|
|
783
815
|
full_name = utils.format_task_name(name, params)
|
|
784
816
|
self.instances[full_name] = task
|
|
785
817
|
|
|
818
|
+
def require_workspace_resource(self, taskname):
|
|
819
|
+
name, _ = utils.parse_task_name(taskname)
|
|
820
|
+
task = self.get_task_class(name)
|
|
821
|
+
raise_task_error_if(task is None, name, "Resource not found")
|
|
822
|
+
raise_task_error_if(not issubclass(task, WorkspaceResource), name, "Not a workspace resource")
|
|
823
|
+
self._workspace_resources.append(taskname)
|
|
824
|
+
|
|
786
825
|
def get_task_class(self, name):
|
|
787
826
|
return self.tasks.get(name)
|
|
788
827
|
|
|
789
828
|
def get_task_classes(self):
|
|
790
829
|
return list(self.tasks.values())
|
|
791
830
|
|
|
792
|
-
def get_task(self, name, extra_params=None, manifest=None):
|
|
831
|
+
def get_task(self, name, extra_params=None, manifest=None, buildenv=None):
|
|
793
832
|
name, params = utils.parse_task_name(name)
|
|
794
833
|
params.update(extra_params or {})
|
|
795
834
|
full_name = utils.format_task_name(name, params)
|
|
@@ -800,14 +839,19 @@ class TaskRegistry(object):
|
|
|
800
839
|
|
|
801
840
|
cls = self.tasks.get(name)
|
|
802
841
|
if cls:
|
|
803
|
-
task = cls(parameters=params, manifest=manifest)
|
|
842
|
+
task = cls(parameters=params, manifest=manifest, buildenv=buildenv)
|
|
804
843
|
task = self.instances.get(task.qualified_name, task)
|
|
805
|
-
|
|
806
|
-
|
|
844
|
+
if not isinstance(task, Resource) or isinstance(task, WorkspaceResource):
|
|
845
|
+
self.instances[task.qualified_name] = task
|
|
846
|
+
self.instances[full_name] = task
|
|
807
847
|
return task
|
|
808
848
|
|
|
809
849
|
raise_task_error_if(not task, full_name, "No such task")
|
|
810
850
|
|
|
851
|
+
def has_task(self, name):
|
|
852
|
+
name, params = utils.parse_task_name(name)
|
|
853
|
+
return self.tasks.get(name) is not None
|
|
854
|
+
|
|
811
855
|
def set_default_parameters(self, task):
|
|
812
856
|
name, params = utils.parse_task_name(task)
|
|
813
857
|
|
|
@@ -880,18 +924,195 @@ class TaskGenerator(object):
|
|
|
880
924
|
|
|
881
925
|
class attributes:
|
|
882
926
|
@staticmethod
|
|
883
|
-
def
|
|
927
|
+
def arch(cls):
|
|
928
|
+
""" Return the architecture name (x86_64, arm64, etc.). """
|
|
929
|
+
|
|
930
|
+
cls._arch = Export(lambda t: platform.machine().lower().replace("amd64", "x86_64"))
|
|
931
|
+
cls.arch = property(lambda t: t._arch.value)
|
|
932
|
+
return cls
|
|
933
|
+
|
|
934
|
+
@staticmethod
|
|
935
|
+
def artifact(name, session=False):
|
|
936
|
+
"""Decorator adding an additional artifact to a task.
|
|
937
|
+
|
|
938
|
+
Jolt calls the new publish method `publish_<name>` with the
|
|
939
|
+
new artifact as argument. Non-alphanumeric characters in the
|
|
940
|
+
name are replaced with underscores (_). The new artifact is
|
|
941
|
+
consumable from another task by using the string format
|
|
942
|
+
`<artifact-name>@<task-name>` to index dependencies. The
|
|
943
|
+
standard artifact is named `main`. See the example below.
|
|
944
|
+
|
|
945
|
+
If `session` is `True`, the new artifact will be a session
|
|
946
|
+
artifact that is only valid during a single Jolt invokation.
|
|
947
|
+
Session artifacts are published even if the task fails and may
|
|
948
|
+
be used to save logs and data for post-mortem analysis.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
name (str): Name of artifact. Used as reference from
|
|
952
|
+
consuming tasks.
|
|
953
|
+
session (boolean, False): Session artifact.
|
|
954
|
+
|
|
955
|
+
Example:
|
|
956
|
+
|
|
957
|
+
.. literalinclude:: ../examples/artifacts/build.jolt
|
|
958
|
+
:language: python
|
|
959
|
+
:caption: examples/artifacts/build.jolt
|
|
960
|
+
|
|
884
961
|
"""
|
|
885
|
-
|
|
962
|
+
name = utils.canonical(name)
|
|
886
963
|
|
|
887
|
-
|
|
888
|
-
|
|
964
|
+
def decorate(cls):
|
|
965
|
+
_old_artifacts = cls._artifacts
|
|
966
|
+
|
|
967
|
+
@functools.wraps(cls._artifacts)
|
|
968
|
+
def _artifacts(self, cache, node):
|
|
969
|
+
artifacts = _old_artifacts(self, cache, node)
|
|
970
|
+
artifacts += [cache.get_artifact(node, name, session=session or isinstance(cls, Resource))]
|
|
971
|
+
return artifacts
|
|
972
|
+
|
|
973
|
+
cls._artifacts = _artifacts
|
|
974
|
+
return cls
|
|
975
|
+
|
|
976
|
+
return decorate
|
|
977
|
+
|
|
978
|
+
@staticmethod
|
|
979
|
+
def artifact_upload(uri, name="main", condition=None):
|
|
980
|
+
"""
|
|
981
|
+
Decorator to add uploading of an artifact to a server.
|
|
982
|
+
|
|
983
|
+
Upon successful completion of the task, the resulting
|
|
984
|
+
artifact uploaded to the specified URI. The URI should
|
|
985
|
+
be in the format `protocol://user:password@host/path`.
|
|
986
|
+
|
|
987
|
+
The following protocols are supported:
|
|
988
|
+
|
|
989
|
+
- `http://`
|
|
990
|
+
- `https://`
|
|
991
|
+
- `file://`
|
|
992
|
+
|
|
993
|
+
Local path are also supported in which case the artifact
|
|
994
|
+
is copied to the specified location.
|
|
995
|
+
|
|
996
|
+
If the path ends with a slash, the artifact is treated as
|
|
997
|
+
a directory and copied into the root of the directory.
|
|
998
|
+
|
|
999
|
+
If the path ends with a supported archive extension, the
|
|
1000
|
+
artifact is archived and optionally compressed before being
|
|
1001
|
+
copied. See :func:`jolt.Tools.archive` for supported archive
|
|
1002
|
+
and compression formats.
|
|
1003
|
+
|
|
1004
|
+
Usernames and passwords are optional. If omitted, the
|
|
1005
|
+
artifact is uploaded anonymously. Environment variables
|
|
1006
|
+
can be used to store sensitive information, such as
|
|
1007
|
+
passwords. Specify the environment variable name in the
|
|
1008
|
+
URI as `protocol://{environ[USER]}:{environ[PASS]}@host/path`.
|
|
1009
|
+
|
|
1010
|
+
The upload can be conditioned on the return value of a
|
|
1011
|
+
function. The function is passed the task instance as
|
|
1012
|
+
an argument and should return a boolean value. If the
|
|
1013
|
+
function returns ``False``, the upload is skipped. The value
|
|
1014
|
+
of the condition influences the hash of the task.
|
|
889
1015
|
|
|
890
1016
|
Args:
|
|
891
|
-
|
|
892
|
-
|
|
1017
|
+
condition (str): Condition function to evaluate before
|
|
1018
|
+
uploading the artifact. The function is passed the
|
|
1019
|
+
task instance as an argument and should return a
|
|
1020
|
+
boolean value. By default, the artifact is always
|
|
1021
|
+
uploaded.
|
|
1022
|
+
name (str): Name of the artifact to upload.
|
|
1023
|
+
uri (str): Destination URI for the artifact.
|
|
1024
|
+
|
|
1025
|
+
Example:
|
|
1026
|
+
|
|
1027
|
+
.. literalinclude:: ../examples/artifacts/upload.jolt
|
|
1028
|
+
:language: python
|
|
1029
|
+
:caption: examples/artifacts/upload.jolt
|
|
893
1030
|
"""
|
|
894
|
-
|
|
1031
|
+
|
|
1032
|
+
def decorate(cls):
|
|
1033
|
+
if name == "main":
|
|
1034
|
+
old_publish = cls.publish
|
|
1035
|
+
else:
|
|
1036
|
+
old_publish = getattr(cls, "publish_" + name, None)
|
|
1037
|
+
|
|
1038
|
+
raise_error_if(old_publish is None, f"Cannot upload artifact '{name}' from task '{cls.name or cls.__name__.lower()}', it does not exist")
|
|
1039
|
+
|
|
1040
|
+
def upload_file(self, tools, cwd, src, dst):
|
|
1041
|
+
with tools.cwd(cwd):
|
|
1042
|
+
tools.upload(src, dst + src)
|
|
1043
|
+
|
|
1044
|
+
def copy_file(self, tools, cwd, src, dst):
|
|
1045
|
+
with tools.cwd(cwd):
|
|
1046
|
+
tools.copy(src, dst + src)
|
|
1047
|
+
|
|
1048
|
+
def list_files(self, artifact, tools):
|
|
1049
|
+
with tools.cwd(artifact.path):
|
|
1050
|
+
for path in tools.glob("**"):
|
|
1051
|
+
# Skip directories
|
|
1052
|
+
if tools.isdir(path):
|
|
1053
|
+
continue
|
|
1054
|
+
yield artifact.path, path
|
|
1055
|
+
|
|
1056
|
+
def archive_files(self, artifact, tools, filename):
|
|
1057
|
+
out = tools.builddir(artifact.identity)
|
|
1058
|
+
with tools.cwd(out):
|
|
1059
|
+
tools.archive(artifact.path, filename)
|
|
1060
|
+
yield out, filename
|
|
1061
|
+
|
|
1062
|
+
@functools.wraps(cls.publish)
|
|
1063
|
+
def publish(self, artifact, tools):
|
|
1064
|
+
old_publish(self, artifact, tools)
|
|
1065
|
+
|
|
1066
|
+
if condition and not bool(condition(self)):
|
|
1067
|
+
return
|
|
1068
|
+
|
|
1069
|
+
# Expand keywords
|
|
1070
|
+
uri_exp = tools.expand(uri)
|
|
1071
|
+
|
|
1072
|
+
# Parse URI to determine protocol
|
|
1073
|
+
uri_parsed = urlparse(uri_exp)
|
|
1074
|
+
if uri_parsed.scheme in ["http", "https"]:
|
|
1075
|
+
action = upload_file
|
|
1076
|
+
elif uri_parsed.scheme in ["", "file"]:
|
|
1077
|
+
uri_exp = uri_parsed.path
|
|
1078
|
+
action = copy_file
|
|
1079
|
+
else:
|
|
1080
|
+
raise_task_error(self, f"Unsupported protocol '{uri_parsed.scheme}' in URI '{uri_exp}'")
|
|
1081
|
+
|
|
1082
|
+
if uri_exp.endswith("/"):
|
|
1083
|
+
generator = list_files
|
|
1084
|
+
else:
|
|
1085
|
+
generator = functools.partial(archive_files, filename=fs.path.basename(uri_parsed.path))
|
|
1086
|
+
uri_exp = fs.path.dirname(uri_exp) + "/"
|
|
1087
|
+
|
|
1088
|
+
if action is copy_file:
|
|
1089
|
+
uri_exp = tools.expand_path(uri_exp) + "/"
|
|
1090
|
+
|
|
1091
|
+
for cwd, file in generator(self, artifact, tools):
|
|
1092
|
+
action(self, tools, cwd, file, uri_exp)
|
|
1093
|
+
|
|
1094
|
+
if name == "main":
|
|
1095
|
+
setattr(cls, "publish", publish)
|
|
1096
|
+
else:
|
|
1097
|
+
setattr(cls, "publish_" + name, publish)
|
|
1098
|
+
|
|
1099
|
+
if condition:
|
|
1100
|
+
old_init = cls.__init__
|
|
1101
|
+
|
|
1102
|
+
@functools.wraps(cls.__init__)
|
|
1103
|
+
def new_init(self, *args, **kwargs):
|
|
1104
|
+
old_init(self, *args, **kwargs)
|
|
1105
|
+
|
|
1106
|
+
def make_bool(fn, *args, **kwargs):
|
|
1107
|
+
return bool(fn(*args, **kwargs))
|
|
1108
|
+
|
|
1109
|
+
self.influence.append(CallbackInfluence(f"Upload {name}", make_bool, condition, self))
|
|
1110
|
+
|
|
1111
|
+
cls.__init__ = new_init
|
|
1112
|
+
|
|
1113
|
+
return cls
|
|
1114
|
+
|
|
1115
|
+
return decorate
|
|
895
1116
|
|
|
896
1117
|
@staticmethod
|
|
897
1118
|
def attribute(alias, target, influence=True, default=False):
|
|
@@ -995,6 +1216,79 @@ class attributes:
|
|
|
995
1216
|
return utils.concat_attributes("_publish_files", attrib)(cls)
|
|
996
1217
|
return decorate
|
|
997
1218
|
|
|
1219
|
+
@staticmethod
|
|
1220
|
+
def common_metadata(aclocal=True, cmake=True, cxxinfo=True, path=True, pkgconfig=True):
|
|
1221
|
+
"""
|
|
1222
|
+
Decorator adding common metadata to published artifacts.
|
|
1223
|
+
|
|
1224
|
+
The decorator adds common environment variables and C/C++ build information
|
|
1225
|
+
to the published artifact:
|
|
1226
|
+
|
|
1227
|
+
- Adds `bin/` to `PATH` environment variable if it exists.
|
|
1228
|
+
- Adds `lib/` and `lib64/` to C++ library paths if they exist.
|
|
1229
|
+
- Adds `include/` to C++ include paths if it exists.
|
|
1230
|
+
- Adds `lib/pkgconfig/`, `lib64/pkgconfig/` and `share/pkgconfig/` to
|
|
1231
|
+
`PKG_CONFIG_PATH` environment variable if `.pc` files are found.
|
|
1232
|
+
The `prefix` variable in `.pc` files is relocated to allow
|
|
1233
|
+
installation in arbitrary locations.
|
|
1234
|
+
- Adds `lib/cmake/`, `lib64/cmake/` and `share/cmake/` to
|
|
1235
|
+
`CMAKE_PREFIX_PATH` environment variable if they exist.
|
|
1236
|
+
- Adds `share/aclocal/` to `ACLOCAL_PATH` environment variable if it exists.
|
|
1237
|
+
|
|
1238
|
+
"""
|
|
1239
|
+
def decorate(cls):
|
|
1240
|
+
_old_publish = cls.publish
|
|
1241
|
+
|
|
1242
|
+
@functools.wraps(cls.publish)
|
|
1243
|
+
def publish(self, artifact, tools):
|
|
1244
|
+
_old_publish(self, artifact, tools)
|
|
1245
|
+
|
|
1246
|
+
with tools.cwd(artifact.path):
|
|
1247
|
+
if path and tools.exists("bin"):
|
|
1248
|
+
artifact.environ.PATH.append("bin")
|
|
1249
|
+
|
|
1250
|
+
pcdirs = set()
|
|
1251
|
+
|
|
1252
|
+
for pcpath in tools.glob("lib/pkgconfig/*.pc") + tools.glob("lib64/pkgconfig/*.pc") + tools.glob("share/pkgconfig/*.pc"):
|
|
1253
|
+
pcdirs.add(fs.path.dirname(pcpath))
|
|
1254
|
+
tools.replace_in_file(pcpath, artifact.strings.install_prefix, "${{pcfiledir}}/../..")
|
|
1255
|
+
for pcpath in tools.glob("lib/*/pkgconfig/*.pc") + tools.glob("lib64/*/pkgconfig/*.pc"):
|
|
1256
|
+
pcdirs.add(fs.path.dirname(pcpath))
|
|
1257
|
+
tools.replace_in_file(pcpath, artifact.strings.install_prefix, "${{pcfiledir}}/../../..")
|
|
1258
|
+
|
|
1259
|
+
for pcdir in pcdirs:
|
|
1260
|
+
artifact.environ.PKG_CONFIG_PATH.append(pcdir)
|
|
1261
|
+
|
|
1262
|
+
if cmake and tools.exists("lib/cmake"):
|
|
1263
|
+
artifact.environ.CMAKE_PREFIX_PATH.append(".")
|
|
1264
|
+
if cmake and tools.exists("lib64/cmake"):
|
|
1265
|
+
artifact.environ.CMAKE_PREFIX_PATH.append(".")
|
|
1266
|
+
if cmake and tools.exists("share/cmake"):
|
|
1267
|
+
artifact.environ.CMAKE_PREFIX_PATH.append(".")
|
|
1268
|
+
|
|
1269
|
+
if aclocal and tools.exists("share/aclocal"):
|
|
1270
|
+
artifact.environ.ACLOCAL_PATH.append("share/aclocal")
|
|
1271
|
+
|
|
1272
|
+
cls.publish = publish
|
|
1273
|
+
|
|
1274
|
+
return cls
|
|
1275
|
+
|
|
1276
|
+
return decorate
|
|
1277
|
+
|
|
1278
|
+
@staticmethod
|
|
1279
|
+
def requires(attrib):
|
|
1280
|
+
"""
|
|
1281
|
+
Decorates a task with an alternative ``requires`` attribute.
|
|
1282
|
+
|
|
1283
|
+
The new attribute will be concatenated with the regular
|
|
1284
|
+
``requires`` attribute.
|
|
1285
|
+
|
|
1286
|
+
Args:
|
|
1287
|
+
attrib (str): Name of alternative attribute.
|
|
1288
|
+
Keywords are expanded.
|
|
1289
|
+
"""
|
|
1290
|
+
return utils.concat_attributes("requires", attrib)
|
|
1291
|
+
|
|
998
1292
|
@staticmethod
|
|
999
1293
|
def load(filepath):
|
|
1000
1294
|
"""
|
|
@@ -1066,6 +1360,20 @@ class attributes:
|
|
|
1066
1360
|
return cls
|
|
1067
1361
|
return _decorate
|
|
1068
1362
|
|
|
1363
|
+
@staticmethod
|
|
1364
|
+
def platform(attrib):
|
|
1365
|
+
"""
|
|
1366
|
+
Decorates a task with an alternative ``platform`` attribute.
|
|
1367
|
+
|
|
1368
|
+
The new attribute will be concatenated with the regular
|
|
1369
|
+
``platform`` attribute.
|
|
1370
|
+
|
|
1371
|
+
Args:
|
|
1372
|
+
attrib (str): Name of alternative attribute.
|
|
1373
|
+
Keywords are expanded.
|
|
1374
|
+
"""
|
|
1375
|
+
return utils.concat_attributes("platform", attrib)
|
|
1376
|
+
|
|
1069
1377
|
@staticmethod
|
|
1070
1378
|
def system(cls):
|
|
1071
1379
|
"""
|
|
@@ -1077,6 +1385,40 @@ class attributes:
|
|
|
1077
1385
|
cls.system = property(lambda t: t._system.value)
|
|
1078
1386
|
return cls
|
|
1079
1387
|
|
|
1388
|
+
@staticmethod
|
|
1389
|
+
def timeout(seconds):
|
|
1390
|
+
"""
|
|
1391
|
+
Decorator setting a timeout for a task.
|
|
1392
|
+
|
|
1393
|
+
The timeout applies to the task's run method. A JoltTimeoutError
|
|
1394
|
+
is raised if the task does not complete within the specified time.
|
|
1395
|
+
|
|
1396
|
+
Args:
|
|
1397
|
+
seconds (int): Timeout in seconds.
|
|
1398
|
+
|
|
1399
|
+
Example:
|
|
1400
|
+
|
|
1401
|
+
.. code-block:: python
|
|
1402
|
+
|
|
1403
|
+
@attributes.timeout(5)
|
|
1404
|
+
class Example(Task):
|
|
1405
|
+
def run(self, deps, tools):
|
|
1406
|
+
time.sleep(10)
|
|
1407
|
+
|
|
1408
|
+
"""
|
|
1409
|
+
def decorate(cls):
|
|
1410
|
+
_old_run = cls.run
|
|
1411
|
+
|
|
1412
|
+
@functools.wraps(cls.run)
|
|
1413
|
+
def run(self, deps, tools):
|
|
1414
|
+
with tools.timeout(seconds):
|
|
1415
|
+
_old_run(self, deps, tools)
|
|
1416
|
+
|
|
1417
|
+
cls.run = run
|
|
1418
|
+
return cls
|
|
1419
|
+
|
|
1420
|
+
return decorate
|
|
1421
|
+
|
|
1080
1422
|
|
|
1081
1423
|
class TaskBase(object):
|
|
1082
1424
|
""" Task base class. """
|
|
@@ -1160,9 +1502,37 @@ class TaskBase(object):
|
|
|
1160
1502
|
joltproject = None
|
|
1161
1503
|
""" Name of project this task belongs to. """
|
|
1162
1504
|
|
|
1505
|
+
local = False
|
|
1506
|
+
""" A local task is only executed on the local node where the build is initiated. """
|
|
1507
|
+
|
|
1163
1508
|
name = None
|
|
1164
1509
|
""" Name of the task. Derived from class name if not set. """
|
|
1165
1510
|
|
|
1511
|
+
platform = {}
|
|
1512
|
+
"""
|
|
1513
|
+
Dictionary of task platform requirements.
|
|
1514
|
+
|
|
1515
|
+
Platform requirements control where tasks are allowed to execute.
|
|
1516
|
+
Multiple requirement key/values may be specified in which case all
|
|
1517
|
+
must be fulfilled in order for the task to be schedulable on a node.
|
|
1518
|
+
|
|
1519
|
+
The following builtin requirement labels exist:
|
|
1520
|
+
|
|
1521
|
+
- node.arch ["arm", "amd64"]
|
|
1522
|
+
- node.os ["linux", "windows"]
|
|
1523
|
+
|
|
1524
|
+
Example:
|
|
1525
|
+
|
|
1526
|
+
.. code-block:: python
|
|
1527
|
+
|
|
1528
|
+
class Hello(Task):
|
|
1529
|
+
# This task must run on Linux.
|
|
1530
|
+
platform = {
|
|
1531
|
+
"node.os": "linux",
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
"""
|
|
1535
|
+
|
|
1166
1536
|
requires = []
|
|
1167
1537
|
""" List of dependencies to other tasks. """
|
|
1168
1538
|
|
|
@@ -1209,7 +1579,15 @@ class TaskBase(object):
|
|
|
1209
1579
|
execution requests, the instance ID won't be.
|
|
1210
1580
|
"""
|
|
1211
1581
|
|
|
1212
|
-
|
|
1582
|
+
@property
|
|
1583
|
+
def instance(self):
|
|
1584
|
+
return str(self._instance.value)
|
|
1585
|
+
|
|
1586
|
+
@instance.setter
|
|
1587
|
+
def instance(self, value):
|
|
1588
|
+
self._instance.assign(value)
|
|
1589
|
+
|
|
1590
|
+
def __init__(self, parameters=None, manifest=None, buildenv=None, **kwargs):
|
|
1213
1591
|
self._identity = None
|
|
1214
1592
|
self._report = _JoltTask()
|
|
1215
1593
|
self.name = self.__class__.name
|
|
@@ -1230,6 +1608,7 @@ class TaskBase(object):
|
|
|
1230
1608
|
self.selfsustained = utils.call_or_return(self, self.__class__._selfsustained)
|
|
1231
1609
|
self.tools = Tools(self, self.joltdir)
|
|
1232
1610
|
self._apply_manifest(manifest)
|
|
1611
|
+
self._apply_protobuf(buildenv)
|
|
1233
1612
|
self.requires = self.expand(self.requires)
|
|
1234
1613
|
|
|
1235
1614
|
def _apply_manifest(self, manifest):
|
|
@@ -1246,6 +1625,26 @@ class TaskBase(object):
|
|
|
1246
1625
|
.format(attrib.name, self.qualified_name)
|
|
1247
1626
|
export.assign(attrib.value)
|
|
1248
1627
|
|
|
1628
|
+
def _apply_protobuf(self, buildenv):
|
|
1629
|
+
if buildenv is None:
|
|
1630
|
+
return
|
|
1631
|
+
task = buildenv.tasks.get(self.exported_name)
|
|
1632
|
+
if not task:
|
|
1633
|
+
return
|
|
1634
|
+
if task.identity:
|
|
1635
|
+
self.identity = task.identity
|
|
1636
|
+
if task.taint:
|
|
1637
|
+
self.taint = task.taint
|
|
1638
|
+
for prop in task.properties:
|
|
1639
|
+
export = utils.getattr_safe(self, prop.key)
|
|
1640
|
+
assert isinstance(export, Export), \
|
|
1641
|
+
"'{0}' is not an exportable attribute of task '{1}'"\
|
|
1642
|
+
.format(prop.key, self.qualified_name)
|
|
1643
|
+
export.assign(prop.value)
|
|
1644
|
+
|
|
1645
|
+
def _artifacts(self, cache, node):
|
|
1646
|
+
return [cache.get_artifact(node, "main")]
|
|
1647
|
+
|
|
1249
1648
|
def _influence(self):
|
|
1250
1649
|
return utils.as_list(self.__class__.influence)
|
|
1251
1650
|
|
|
@@ -1305,6 +1704,9 @@ class TaskBase(object):
|
|
|
1305
1704
|
"Required parameter '{0}' has not been set", key)
|
|
1306
1705
|
|
|
1307
1706
|
def _verify_influence(self, deps, artifact, tools, sources=None):
|
|
1707
|
+
if "configurationrepository" in self.name:
|
|
1708
|
+
breakpoint()
|
|
1709
|
+
|
|
1308
1710
|
# Verify that any transformed sources are influencing
|
|
1309
1711
|
sources = set(map(tools.expand_path, sources or []))
|
|
1310
1712
|
|
|
@@ -1321,29 +1723,29 @@ class TaskBase(object):
|
|
|
1321
1723
|
return not fs.is_relative_to(fname, rootpath)
|
|
1322
1724
|
return _filter
|
|
1323
1725
|
|
|
1726
|
+
# Ignore any files in build directories
|
|
1727
|
+
sources = filter(_subpath_filter(tools.expand_path(tools.buildroot)), sources)
|
|
1728
|
+
|
|
1324
1729
|
for _, dep in deps.items():
|
|
1325
|
-
deptask = dep.
|
|
1730
|
+
deptask = dep.task
|
|
1326
1731
|
if isinstance(deptask, FileInfluence):
|
|
1327
1732
|
# Resource dependencies may cover the influence implicitly
|
|
1328
1733
|
deppath = self.tools.expand_path(str(deptask.path))
|
|
1329
|
-
sources =
|
|
1734
|
+
sources = filter(lambda d, dt=deptask: not dt.is_influenced_by(self, d), sources)
|
|
1330
1735
|
else:
|
|
1331
1736
|
# Ignore any files in artifacts
|
|
1332
1737
|
deppath = self.tools.expand_path(dep.path)
|
|
1333
|
-
sources =
|
|
1334
|
-
|
|
1335
|
-
# Ignore any files in build directories
|
|
1336
|
-
sources = filter(_subpath_filter(tools.expand_path(tools.buildroot)), sources)
|
|
1337
|
-
sources = set(sources)
|
|
1738
|
+
sources = filter(_subpath_filter(deppath), sources)
|
|
1338
1739
|
|
|
1339
1740
|
for ip in self.influence:
|
|
1340
1741
|
if not isinstance(ip, FileInfluence):
|
|
1341
1742
|
continue
|
|
1342
|
-
|
|
1343
|
-
|
|
1743
|
+
sources = {source for source in sources if not ip.is_influenced_by(self, source)}
|
|
1744
|
+
|
|
1344
1745
|
for source in sources:
|
|
1345
1746
|
log.warning("Missing influence: {} ({})", source, self.name)
|
|
1346
|
-
|
|
1747
|
+
|
|
1748
|
+
raise_task_error_if(set(sources), self, "Task is missing source influence")
|
|
1347
1749
|
|
|
1348
1750
|
def _get_export_objects(self):
|
|
1349
1751
|
return self._exports
|
|
@@ -1384,6 +1786,16 @@ class TaskBase(object):
|
|
|
1384
1786
|
self.name,
|
|
1385
1787
|
self._get_explicitly_set_parameters())
|
|
1386
1788
|
|
|
1789
|
+
@property
|
|
1790
|
+
def exported_name(self):
|
|
1791
|
+
if hasattr(self, "_exported_name"):
|
|
1792
|
+
return self._exported_name
|
|
1793
|
+
return self.short_qualified_name
|
|
1794
|
+
|
|
1795
|
+
@exported_name.setter
|
|
1796
|
+
def exported_name(self, name):
|
|
1797
|
+
self._exported_name = name
|
|
1798
|
+
|
|
1387
1799
|
def expand(self, string_or_list, *args, **kwargs):
|
|
1388
1800
|
""" Expands keyword arguments/macros in a format string.
|
|
1389
1801
|
|
|
@@ -1422,8 +1834,15 @@ class Task(TaskBase):
|
|
|
1422
1834
|
Abstract tasks can't be executed and won't be listed.
|
|
1423
1835
|
"""
|
|
1424
1836
|
|
|
1837
|
+
unstable = False
|
|
1838
|
+
"""
|
|
1839
|
+
An unstable task is allowed to fail without stopping or failing the entire build.
|
|
1840
|
+
|
|
1841
|
+
The unstable task is still reported as a failure at the end of the build.
|
|
1842
|
+
"""
|
|
1843
|
+
|
|
1425
1844
|
def __init__(self, parameters=None, **kwargs):
|
|
1426
|
-
super(
|
|
1845
|
+
super().__init__(parameters, **kwargs)
|
|
1427
1846
|
|
|
1428
1847
|
def info(self, fmt, *args, **kwargs):
|
|
1429
1848
|
"""
|
|
@@ -1478,6 +1897,9 @@ class Task(TaskBase):
|
|
|
1478
1897
|
as ``target`` above, are automatically expanded to their values.
|
|
1479
1898
|
"""
|
|
1480
1899
|
|
|
1900
|
+
def nopublish(self, artifact, tools):
|
|
1901
|
+
raise NotImplementedError()
|
|
1902
|
+
|
|
1481
1903
|
def publish(self, artifact, tools):
|
|
1482
1904
|
"""
|
|
1483
1905
|
Publishes files produced by :func:`~run`.
|
|
@@ -1512,7 +1934,7 @@ class Task(TaskBase):
|
|
|
1512
1934
|
"""
|
|
1513
1935
|
raise NotImplementedError()
|
|
1514
1936
|
|
|
1515
|
-
def
|
|
1937
|
+
def debugshell(self, deps, tools):
|
|
1516
1938
|
"""
|
|
1517
1939
|
Invoked to start a debug shell.
|
|
1518
1940
|
|
|
@@ -1543,7 +1965,7 @@ class SubTask(object):
|
|
|
1543
1965
|
self._message = None
|
|
1544
1966
|
self._outputs = []
|
|
1545
1967
|
self._task = task
|
|
1546
|
-
self._tools =
|
|
1968
|
+
self._tools = copy.copy(task.tools)
|
|
1547
1969
|
|
|
1548
1970
|
def __str__(self):
|
|
1549
1971
|
if self.message:
|
|
@@ -1629,15 +2051,21 @@ class SubTask(object):
|
|
|
1629
2051
|
self._influence.append(infl)
|
|
1630
2052
|
|
|
1631
2053
|
def add_influence_file(self, path):
|
|
2054
|
+
path = self._tools.expand_path(path)
|
|
1632
2055
|
self.add_influence(utils.filesha1(path))
|
|
1633
2056
|
|
|
1634
2057
|
def add_influence_depfile(self, path):
|
|
1635
2058
|
def depfile():
|
|
1636
2059
|
result = ""
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2060
|
+
try:
|
|
2061
|
+
deps = self._tools.read_depfile(fs.path.join(self._task.joltdir, path))
|
|
2062
|
+
except OSError:
|
|
2063
|
+
return "N/A"
|
|
2064
|
+
with self._tools.cwd(self._task.joltdir):
|
|
2065
|
+
for output in self.outputs:
|
|
2066
|
+
for input in deps.get(output, []):
|
|
2067
|
+
input = self._tools.expand_path(input)
|
|
2068
|
+
result += utils.filesha1(input)
|
|
1641
2069
|
return result
|
|
1642
2070
|
self.add_influence(depfile)
|
|
1643
2071
|
|
|
@@ -1683,7 +2111,7 @@ class CommandSubtask(SubTask):
|
|
|
1683
2111
|
|
|
1684
2112
|
def __str__(self):
|
|
1685
2113
|
s = super().__str__()
|
|
1686
|
-
return s if s is not None and not log.is_verbose() else self._command
|
|
2114
|
+
return s if s is not None and not log.is_verbose() else self._tools.expand(self._command)
|
|
1687
2115
|
|
|
1688
2116
|
def run(self):
|
|
1689
2117
|
self._tools.run(self._command)
|
|
@@ -1770,11 +2198,11 @@ class MultiTask(Task):
|
|
|
1770
2198
|
|
|
1771
2199
|
def __init__(self, *args, **kwargs):
|
|
1772
2200
|
super().__init__(*args, **kwargs)
|
|
1773
|
-
self._subtasks =
|
|
2201
|
+
self._subtasks = set()
|
|
1774
2202
|
self._subtasks_by_output = {}
|
|
1775
2203
|
|
|
1776
2204
|
def _add_subtask(self, subtask):
|
|
1777
|
-
self._subtasks.
|
|
2205
|
+
self._subtasks.add(subtask)
|
|
1778
2206
|
|
|
1779
2207
|
for output in subtask.outputs:
|
|
1780
2208
|
if output in self._subtasks_by_output:
|
|
@@ -1799,19 +2227,22 @@ class MultiTask(Task):
|
|
|
1799
2227
|
inputsubtask = Input(self, input)
|
|
1800
2228
|
else:
|
|
1801
2229
|
inputsubtask = input
|
|
1802
|
-
self._subtasks.
|
|
2230
|
+
self._subtasks.add(inputsubtask)
|
|
1803
2231
|
self._subtasks_by_output[input] = inputsubtask
|
|
1804
2232
|
return inputsubtask
|
|
1805
2233
|
|
|
1806
2234
|
def _to_subtask_list(self, inputs):
|
|
1807
2235
|
inputs = utils.as_list(inputs)
|
|
1808
|
-
return [self._add_input(input) for input in inputs]
|
|
2236
|
+
return utils.unique_list([self._add_input(input) for input in inputs])
|
|
1809
2237
|
|
|
1810
2238
|
def _to_output_files(self, subtasks):
|
|
1811
2239
|
subtasks = utils.as_list(subtasks)
|
|
1812
2240
|
outputs = []
|
|
1813
2241
|
for subtask in subtasks:
|
|
1814
|
-
|
|
2242
|
+
if isinstance(subtask, SubTask):
|
|
2243
|
+
outputs.extend(subtask.outputs)
|
|
2244
|
+
else:
|
|
2245
|
+
outputs.append(subtask)
|
|
1815
2246
|
return outputs
|
|
1816
2247
|
|
|
1817
2248
|
def _to_input_subtasks(self, inputs, **kwargs):
|
|
@@ -1851,12 +2282,11 @@ class MultiTask(Task):
|
|
|
1851
2282
|
outputs=["executable"])
|
|
1852
2283
|
|
|
1853
2284
|
"""
|
|
1854
|
-
|
|
2285
|
+
input_jobs = self._to_input_subtasks(inputs, **kwargs)
|
|
1855
2286
|
inputfiles = self._to_output_files(inputs)
|
|
1856
2287
|
|
|
1857
2288
|
outputs = utils.as_list(outputs)
|
|
1858
|
-
outputs = [self.
|
|
1859
|
-
outputs = [fs.path.relpath(output) for output in outputs]
|
|
2289
|
+
outputs = [self.tools.expand_relpath(output, self.joltdir, **kwargs) for output in outputs]
|
|
1860
2290
|
|
|
1861
2291
|
dirs = set()
|
|
1862
2292
|
if mkdir:
|
|
@@ -1868,7 +2298,7 @@ class MultiTask(Task):
|
|
|
1868
2298
|
|
|
1869
2299
|
for dir in dirs:
|
|
1870
2300
|
subtask.add_dependency(dir)
|
|
1871
|
-
for input in
|
|
2301
|
+
for input in input_jobs:
|
|
1872
2302
|
subtask.add_dependency(input)
|
|
1873
2303
|
for output in outputs:
|
|
1874
2304
|
output = self.expand(output, inputs=inputfiles, outputs=outputs, **kwargs)
|
|
@@ -2051,7 +2481,6 @@ class MultiTask(Task):
|
|
|
2051
2481
|
|
|
2052
2482
|
This method should typically not be overridden in subclasses.
|
|
2053
2483
|
"""
|
|
2054
|
-
|
|
2055
2484
|
self.generate(deps, tools)
|
|
2056
2485
|
|
|
2057
2486
|
log.debug("About to start executing these subtasks:")
|
|
@@ -2070,6 +2499,7 @@ class MultiTask(Task):
|
|
|
2070
2499
|
for subtask in self._subtasks:
|
|
2071
2500
|
if subtask not in subtasks:
|
|
2072
2501
|
subtasks[subtask] = []
|
|
2502
|
+
if subtask not in deps:
|
|
2073
2503
|
deps[subtask] = []
|
|
2074
2504
|
for dep in subtask.dependencies:
|
|
2075
2505
|
if dep not in deps:
|
|
@@ -2079,12 +2509,18 @@ class MultiTask(Task):
|
|
|
2079
2509
|
|
|
2080
2510
|
# Prune up-to-date subtasks
|
|
2081
2511
|
for subtask in list(filter(lambda subtask: not subtask.is_outdated, subtasks.keys())):
|
|
2512
|
+
log.debug("Pruning {}", subtask)
|
|
2082
2513
|
del subtasks[subtask]
|
|
2083
2514
|
for dep in deps[subtask]:
|
|
2084
|
-
|
|
2515
|
+
try:
|
|
2516
|
+
subtasks[dep].remove(subtask)
|
|
2517
|
+
except KeyError:
|
|
2518
|
+
pass
|
|
2519
|
+
|
|
2520
|
+
self.subtaskindex = 0
|
|
2521
|
+
self.subtaskcount = len(subtasks)
|
|
2085
2522
|
|
|
2086
|
-
|
|
2087
|
-
subtaskcount = len(subtasks)
|
|
2523
|
+
lock = RLock()
|
|
2088
2524
|
|
|
2089
2525
|
with ThreadPoolExecutor(max_workers=tools.cpu_count()) as pool:
|
|
2090
2526
|
futures = {}
|
|
@@ -2097,11 +2533,14 @@ class MultiTask(Task):
|
|
|
2097
2533
|
break
|
|
2098
2534
|
|
|
2099
2535
|
for subtask in candidates:
|
|
2100
|
-
subtaskindex += 1
|
|
2101
2536
|
del subtasks[subtask]
|
|
2102
2537
|
if subtask.is_outdated:
|
|
2103
|
-
|
|
2104
|
-
|
|
2538
|
+
def runner(subtask):
|
|
2539
|
+
with lock:
|
|
2540
|
+
self.subtaskindex += 1
|
|
2541
|
+
log.info("[{}/{}] {}", self.subtaskindex, self.subtaskcount, str(subtask))
|
|
2542
|
+
subtask.run()
|
|
2543
|
+
futures[pool.submit(functools.partial(runner, subtask))] = subtask
|
|
2105
2544
|
else:
|
|
2106
2545
|
completed.append(subtask)
|
|
2107
2546
|
|
|
@@ -2132,6 +2571,13 @@ class MultiTask(Task):
|
|
|
2132
2571
|
|
|
2133
2572
|
raise_task_error_if(subtasks, self, "Subtasks with unresolved dependencies could not be executed")
|
|
2134
2573
|
|
|
2574
|
+
def inputs(self, jobs):
|
|
2575
|
+
return self._to_subtask_list(jobs)
|
|
2576
|
+
|
|
2577
|
+
def outputs(self, jobs):
|
|
2578
|
+
jobs = utils.as_list(jobs)
|
|
2579
|
+
return [output for job in jobs for output in job.outputs]
|
|
2580
|
+
|
|
2135
2581
|
|
|
2136
2582
|
class Runner(Task):
|
|
2137
2583
|
"""
|
|
@@ -2212,9 +2658,9 @@ class Runner(Task):
|
|
|
2212
2658
|
found = False
|
|
2213
2659
|
|
|
2214
2660
|
for task, artifact in deps.items():
|
|
2215
|
-
if not artifact.
|
|
2661
|
+
if not artifact.task.is_cacheable():
|
|
2216
2662
|
continue
|
|
2217
|
-
if artifact.strings.executable
|
|
2663
|
+
if artifact.strings.executable is None:
|
|
2218
2664
|
self.verbose("No executable found in task artifact for '{}'", task)
|
|
2219
2665
|
continue
|
|
2220
2666
|
with tools.cwd(artifact.path):
|
|
@@ -2233,6 +2679,22 @@ class ErrorProxy(object):
|
|
|
2233
2679
|
def __init__(self, error):
|
|
2234
2680
|
self._error = error
|
|
2235
2681
|
|
|
2682
|
+
@property
|
|
2683
|
+
def type(self):
|
|
2684
|
+
return self._error.type
|
|
2685
|
+
|
|
2686
|
+
@type.setter
|
|
2687
|
+
def type(self, value):
|
|
2688
|
+
self._error.type = value
|
|
2689
|
+
|
|
2690
|
+
@property
|
|
2691
|
+
def details(self):
|
|
2692
|
+
return self._error.details
|
|
2693
|
+
|
|
2694
|
+
@property
|
|
2695
|
+
def location(self):
|
|
2696
|
+
return self._error.location
|
|
2697
|
+
|
|
2236
2698
|
@property
|
|
2237
2699
|
def message(self):
|
|
2238
2700
|
return self._error.message
|
|
@@ -2240,11 +2702,21 @@ class ErrorProxy(object):
|
|
|
2240
2702
|
|
|
2241
2703
|
class ReportProxy(object):
|
|
2242
2704
|
def __init__(self, task, report):
|
|
2705
|
+
from jolt import config
|
|
2243
2706
|
self._task = task
|
|
2244
2707
|
self._report = report
|
|
2708
|
+
self._max_errors = config.getint("jolt", "task_max_errors", 100)
|
|
2245
2709
|
|
|
2246
2710
|
def add_error(self, type, location, message, details=""):
|
|
2247
2711
|
""" Add an error to the build report. """
|
|
2712
|
+
if len(self.errors) >= self._max_errors:
|
|
2713
|
+
if not hasattr(self._report, "truncated"):
|
|
2714
|
+
error = self._report.create_error()
|
|
2715
|
+
error.type = "Error"
|
|
2716
|
+
error.message = "Too many errors, list truncated"
|
|
2717
|
+
self._report.truncated = True
|
|
2718
|
+
return None
|
|
2719
|
+
|
|
2248
2720
|
error = self._report.create_error()
|
|
2249
2721
|
error.type = type
|
|
2250
2722
|
error.location = location
|
|
@@ -2265,11 +2737,8 @@ class ReportProxy(object):
|
|
|
2265
2737
|
"""
|
|
2266
2738
|
for match in re.finditer(regex, logbuf, re.MULTILINE):
|
|
2267
2739
|
error = match.groupdict()
|
|
2268
|
-
self.add_error(
|
|
2269
|
-
|
|
2270
|
-
error.get("location", ""),
|
|
2271
|
-
error.get("message", ""),
|
|
2272
|
-
error.get("details", ""))
|
|
2740
|
+
if not self.add_error(type, error.get("location", ""), error.get("message", ""), error.get("details", "")):
|
|
2741
|
+
break
|
|
2273
2742
|
|
|
2274
2743
|
def add_regex_errors_with_file(self, type, regex, logbuf, reldir, filterfn=lambda n: True):
|
|
2275
2744
|
"""
|
|
@@ -2290,20 +2759,29 @@ class ReportProxy(object):
|
|
|
2290
2759
|
if not filterfn(error):
|
|
2291
2760
|
continue
|
|
2292
2761
|
if error["location"] not in errors_by_location:
|
|
2293
|
-
errors_by_location[error["location"]] = (error, [error["message"]])
|
|
2762
|
+
errors_by_location[error["location"]] = (error, [error["message"]], error.get("details", ""))
|
|
2294
2763
|
else:
|
|
2295
2764
|
errors_by_location[error["location"]][1].append(error["message"])
|
|
2296
2765
|
|
|
2297
|
-
for error, msgs in errors_by_location.values():
|
|
2766
|
+
for error, msgs, details in errors_by_location.values():
|
|
2298
2767
|
message = "\n".join(utils.unique_list(msgs))
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2768
|
+
if not details:
|
|
2769
|
+
with self._task.tools.cwd(self._task.tools.wsroot):
|
|
2770
|
+
try:
|
|
2771
|
+
details = self._task.tools.read_file(error["file"])
|
|
2772
|
+
details = details.splitlines()
|
|
2773
|
+
details = str(error["line"]) + ": " + details[int(error["line"]) - 1]
|
|
2774
|
+
except Exception:
|
|
2775
|
+
details = ""
|
|
2776
|
+
|
|
2777
|
+
location = error.get("location", "")
|
|
2778
|
+
if location:
|
|
2779
|
+
with self._task.tools.cwd(self._task.tools.wsroot):
|
|
2780
|
+
location = self._task.tools.expand_path(location)
|
|
2781
|
+
location = self._task.tools.expand_relpath(location, self._task.tools.wsroot)
|
|
2782
|
+
|
|
2783
|
+
if not self.add_error(type, location, message, details):
|
|
2784
|
+
break
|
|
2307
2785
|
|
|
2308
2786
|
def add_exception(self, exc, errtype=None, location=None):
|
|
2309
2787
|
"""
|
|
@@ -2317,13 +2795,14 @@ class ReportProxy(object):
|
|
|
2317
2795
|
|
|
2318
2796
|
"""
|
|
2319
2797
|
tb = traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
|
|
2798
|
+
|
|
2320
2799
|
installdir = fs.path.dirname(__file__)
|
|
2321
2800
|
if any(map(lambda frame: installdir not in frame, tb[1:-1])):
|
|
2322
2801
|
while len(tb) > 2 and installdir in tb[1]:
|
|
2323
2802
|
del tb[1]
|
|
2324
|
-
loc = re.findall("\"
|
|
2803
|
+
loc = re.findall("(\".*?\", line [0-9]+, in .*?)\n", tb[1])
|
|
2325
2804
|
location = location or (loc[0] if loc and len(loc) > 0 else "")
|
|
2326
|
-
message =
|
|
2805
|
+
message = log.format_exception_msg(exc)
|
|
2327
2806
|
if isinstance(exc, JoltCommandError):
|
|
2328
2807
|
details = "\n".join(exc.stderr)
|
|
2329
2808
|
elif isinstance(exc, JoltError):
|
|
@@ -2341,10 +2820,26 @@ class ReportProxy(object):
|
|
|
2341
2820
|
def errors(self):
|
|
2342
2821
|
return [ErrorProxy(error) for error in self._report.errors]
|
|
2343
2822
|
|
|
2823
|
+
@errors.setter
|
|
2824
|
+
def errors(self, errlist):
|
|
2825
|
+
assert all(isinstance(err, ErrorProxy) for err in errlist), "Invalid error list"
|
|
2826
|
+
self._report.clear_errors()
|
|
2827
|
+
for err in errlist:
|
|
2828
|
+
self.add_error(err.type, err.location, err.message, err.details)
|
|
2829
|
+
|
|
2344
2830
|
@property
|
|
2345
2831
|
def manifest(self):
|
|
2346
2832
|
return self._report
|
|
2347
2833
|
|
|
2834
|
+
def raise_for_status(self, log_details=False, log_error=False):
|
|
2835
|
+
for error in self.errors:
|
|
2836
|
+
if log_error:
|
|
2837
|
+
log.error("{}: {}", error.type, error.message, context=self._task.identity[:7])
|
|
2838
|
+
if log_details:
|
|
2839
|
+
for line in error.details.splitlines():
|
|
2840
|
+
log.transfer(line, context=self._task.identity[:7])
|
|
2841
|
+
raise LoggedJoltError(JoltError(f"{error.type}: {error.message}"))
|
|
2842
|
+
|
|
2348
2843
|
|
|
2349
2844
|
class Resource(Task):
|
|
2350
2845
|
"""
|
|
@@ -2358,13 +2853,17 @@ class Resource(Task):
|
|
|
2358
2853
|
|
|
2359
2854
|
"""
|
|
2360
2855
|
|
|
2361
|
-
cacheable = False
|
|
2362
|
-
|
|
2363
2856
|
abstract = True
|
|
2364
2857
|
""" An abstract resource class indended to be subclassed. """
|
|
2365
2858
|
|
|
2859
|
+
release_on_error = False
|
|
2860
|
+
""" Call release if an exception occurs during acquire. """
|
|
2861
|
+
|
|
2366
2862
|
def __init__(self, *args, **kwargs):
|
|
2367
|
-
super(
|
|
2863
|
+
super().__init__(*args, **kwargs)
|
|
2864
|
+
|
|
2865
|
+
def _artifacts(self, cache, node):
|
|
2866
|
+
return [cache.get_artifact(node, "main", session=True)]
|
|
2368
2867
|
|
|
2369
2868
|
def is_runnable(self):
|
|
2370
2869
|
return False
|
|
@@ -2425,17 +2924,25 @@ class WorkspaceResource(Resource):
|
|
|
2425
2924
|
"""
|
|
2426
2925
|
|
|
2427
2926
|
def __init__(self, *args, **kwargs):
|
|
2428
|
-
super(
|
|
2927
|
+
super().__init__(*args, **kwargs)
|
|
2429
2928
|
raise_task_error_if(len(self.requires) > 0, self,
|
|
2430
2929
|
"Workspace resource is not allowed to have requirements")
|
|
2431
2930
|
|
|
2432
|
-
def acquire(self, **kwargs):
|
|
2931
|
+
def acquire(self, *args, **kwargs):
|
|
2433
2932
|
return self.acquire_ws()
|
|
2434
2933
|
|
|
2435
|
-
def release(self, **kwargs):
|
|
2934
|
+
def release(self, *args, **kwargs):
|
|
2436
2935
|
return self.release_ws()
|
|
2437
2936
|
|
|
2438
|
-
def
|
|
2937
|
+
def prepare_ws_for(self, task):
|
|
2938
|
+
""" Called to prepare the workspace for a task.
|
|
2939
|
+
|
|
2940
|
+
An implementor overrides this method in a subclass. The method
|
|
2941
|
+
is called before the task influence is calculated and the workspace
|
|
2942
|
+
resource is acquired.
|
|
2943
|
+
"""
|
|
2944
|
+
|
|
2945
|
+
def acquire_ws(self, force=False):
|
|
2439
2946
|
""" Called to acquire the resource.
|
|
2440
2947
|
|
|
2441
2948
|
An implementor overrides this method in a subclass. The acquired
|
|
@@ -2539,6 +3046,7 @@ class Download(Task):
|
|
|
2539
3046
|
Once downloaded, archives are extracted and all of their files are published.
|
|
2540
3047
|
If the file is not an archive it is published as is. Recognized archive extensions are:
|
|
2541
3048
|
|
|
3049
|
+
- .7z
|
|
2542
3050
|
- .tar
|
|
2543
3051
|
- .tar.bz2
|
|
2544
3052
|
- .tar.gz
|
|
@@ -2601,7 +3109,7 @@ class Download(Task):
|
|
|
2601
3109
|
return fs.posixpath.basename(url.path) or "file"
|
|
2602
3110
|
|
|
2603
3111
|
def run(self, deps, tools):
|
|
2604
|
-
supported_formats = [".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
|
|
3112
|
+
supported_formats = [".7z", ".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
|
|
2605
3113
|
|
|
2606
3114
|
raise_task_error_if(not self.url, self, "No URL(s) specified")
|
|
2607
3115
|
|
|
@@ -2698,7 +3206,8 @@ class Script(Task):
|
|
|
2698
3206
|
doc = self.__doc__.split("---", 1)
|
|
2699
3207
|
script = doc[1] if len(doc) > 1 else doc[0]
|
|
2700
3208
|
script = script.splitlines()
|
|
2701
|
-
|
|
3209
|
+
if os_sys.version_info < (3, 13):
|
|
3210
|
+
script = [line[4:] for line in script]
|
|
2702
3211
|
script = "\n".join(script)
|
|
2703
3212
|
script = script.lstrip()
|
|
2704
3213
|
if not script.startswith("#!"):
|
|
@@ -2825,7 +3334,7 @@ class Test(Task):
|
|
|
2825
3334
|
Abstract test tasks can't be executed and won't be listed.
|
|
2826
3335
|
"""
|
|
2827
3336
|
|
|
2828
|
-
|
|
3337
|
+
filter = Parameter(required=False, help="Test-case filter wildcard.")
|
|
2829
3338
|
|
|
2830
3339
|
def __init__(self, *args, **kwargs):
|
|
2831
3340
|
super().__init__(*args, **kwargs)
|
|
@@ -2854,24 +3363,21 @@ class Test(Task):
|
|
|
2854
3363
|
"""
|
|
2855
3364
|
raise_error_if(type(args) is not list, "Test.parameterized() expects a list as argument")
|
|
2856
3365
|
|
|
2857
|
-
|
|
2858
|
-
def
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
retval = super().__get__(obj, cls)
|
|
2864
|
-
retval.__name__ = f"{self.func.__name__}[{self.__index}]"
|
|
2865
|
-
retval.__doc__ = self.func.__doc__
|
|
2866
|
-
return retval
|
|
3366
|
+
def make_method(index, func, *args):
|
|
3367
|
+
def testmethod(self):
|
|
3368
|
+
return func(self, *args)
|
|
3369
|
+
testmethod.__name__ = f"{func.__name__}[{index}]"
|
|
3370
|
+
testmethod.__doc__ = func.__doc__
|
|
3371
|
+
return testmethod
|
|
2867
3372
|
|
|
2868
3373
|
def decorate(method):
|
|
2869
3374
|
frame = sys._getframe().f_back.f_locals
|
|
2870
3375
|
for index, arg in enumerate(args):
|
|
2871
|
-
testmethod =
|
|
3376
|
+
testmethod = make_method(index, method, *utils.as_list(arg))
|
|
2872
3377
|
name = f"{method.__name__}[{index}]"
|
|
2873
3378
|
frame[name] = testmethod
|
|
2874
3379
|
return None
|
|
3380
|
+
|
|
2875
3381
|
return decorate
|
|
2876
3382
|
|
|
2877
3383
|
def setup(self, deps, tools):
|
|
@@ -2980,7 +3486,7 @@ class Test(Task):
|
|
|
2980
3486
|
def run(self, deps, tools):
|
|
2981
3487
|
testsuite = ut.TestSuite()
|
|
2982
3488
|
for test in self._get_test_names():
|
|
2983
|
-
if self.
|
|
3489
|
+
if self.filter.is_unset() or fnmatch.fnmatch(test, str(self.filter)):
|
|
2984
3490
|
testfunc = getattr(self, test)
|
|
2985
3491
|
if not testfunc:
|
|
2986
3492
|
continue
|
|
@@ -3001,7 +3507,7 @@ class Test(Task):
|
|
|
3001
3507
|
|
|
3002
3508
|
|
|
3003
3509
|
@ArtifactAttributeSetProvider.Register
|
|
3004
|
-
class
|
|
3510
|
+
class WorkspaceResourceAttributeSetProvider(ArtifactAttributeSetProvider):
|
|
3005
3511
|
def create(self, artifact):
|
|
3006
3512
|
pass
|
|
3007
3513
|
|
|
@@ -3012,33 +3518,31 @@ class ResourceAttributeSetProvider(ArtifactAttributeSetProvider):
|
|
|
3012
3518
|
pass
|
|
3013
3519
|
|
|
3014
3520
|
def apply(self, task, artifact):
|
|
3015
|
-
resource = artifact.
|
|
3016
|
-
|
|
3017
|
-
|
|
3521
|
+
resource = artifact.task
|
|
3522
|
+
node = artifact.get_node()
|
|
3523
|
+
if not node.is_workspace_resource():
|
|
3524
|
+
return
|
|
3018
3525
|
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3526
|
+
resource.deps = node.cache.get_context(node)
|
|
3527
|
+
resource.deps.__enter__()
|
|
3528
|
+
|
|
3529
|
+
try:
|
|
3530
|
+
resource.acquire(artifact=artifact, deps=resource.deps, tools=resource.tools, owner=task)
|
|
3531
|
+
except (KeyboardInterrupt, Exception) as e:
|
|
3532
|
+
if resource.release_on_error:
|
|
3533
|
+
with utils.ignore_exception():
|
|
3534
|
+
self.unapply(task, artifact)
|
|
3535
|
+
raise e
|
|
3029
3536
|
|
|
3030
3537
|
def unapply(self, task, artifact):
|
|
3031
|
-
resource = artifact.
|
|
3032
|
-
|
|
3033
|
-
|
|
3538
|
+
resource = artifact.task
|
|
3539
|
+
node = artifact.get_node()
|
|
3540
|
+
if not node.is_workspace_resource():
|
|
3541
|
+
return
|
|
3034
3542
|
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
ba = sig.bind_partial(artifact, deps, resource.tools)
|
|
3042
|
-
release = utils.deprecated(resource.release)
|
|
3043
|
-
release(*ba.args, **ba.kwargs)
|
|
3044
|
-
deps.__exit__(None, None, None)
|
|
3543
|
+
try:
|
|
3544
|
+
resource.release(artifact=artifact, deps=resource.deps, tools=resource.tools, owner=task)
|
|
3545
|
+
except Exception as e:
|
|
3546
|
+
raise e
|
|
3547
|
+
|
|
3548
|
+
resource.deps.__exit__(None, None, None)
|