jolt 0.9.172__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 +596 -252
- jolt/chroot.py +36 -11
- jolt/cli.py +143 -130
- jolt/common_pb2.py +45 -45
- jolt/config.py +76 -40
- jolt/error.py +19 -4
- jolt/filesystem.py +2 -6
- jolt/graph.py +400 -82
- jolt/influence.py +110 -3
- jolt/loader.py +338 -174
- jolt/log.py +127 -31
- jolt/manifest.py +13 -46
- jolt/options.py +35 -11
- 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 +2 -2
- jolt/plugins/autotools.py +66 -0
- jolt/plugins/cache.py +1 -1
- jolt/plugins/cmake.py +74 -6
- jolt/plugins/conan.py +238 -0
- jolt/plugins/cxxinfo.py +7 -0
- jolt/plugins/docker.py +76 -19
- jolt/plugins/email.xslt +141 -118
- jolt/plugins/environ.py +11 -0
- jolt/plugins/fetch.py +141 -0
- jolt/plugins/gdb.py +33 -14
- jolt/plugins/gerrit.py +0 -13
- jolt/plugins/git.py +248 -66
- jolt/plugins/googletest.py +1 -1
- jolt/plugins/http.py +1 -1
- 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 +96 -28
- jolt/plugins/ninja.py +424 -150
- jolt/plugins/paths.py +11 -1
- jolt/plugins/pkgconfig.py +219 -0
- jolt/plugins/podman.py +131 -87
- jolt/plugins/python.py +137 -0
- jolt/plugins/remote_execution/administration_pb2.py +27 -19
- jolt/plugins/remote_execution/log_pb2.py +12 -12
- jolt/plugins/remote_execution/scheduler_pb2.py +23 -23
- jolt/plugins/remote_execution/worker_pb2.py +19 -19
- jolt/plugins/report.py +7 -2
- jolt/plugins/rust.py +25 -0
- jolt/plugins/scheduler.py +135 -86
- jolt/plugins/selfdeploy/setup.py +6 -6
- jolt/plugins/selfdeploy.py +49 -31
- jolt/plugins/strings.py +35 -22
- jolt/plugins/symlinks.py +11 -4
- jolt/plugins/telemetry.py +1 -2
- jolt/plugins/timeline.py +13 -3
- jolt/scheduler.py +467 -165
- jolt/tasks.py +427 -111
- jolt/templates/timeline.html.template +44 -47
- jolt/timer.py +22 -0
- jolt/tools.py +527 -188
- jolt/utils.py +183 -3
- jolt/version.py +1 -1
- jolt/xmldom.py +12 -2
- {jolt-0.9.172.dist-info → jolt-0.9.435.dist-info}/METADATA +97 -41
- jolt-0.9.435.dist-info/RECORD +207 -0
- {jolt-0.9.172.dist-info → jolt-0.9.435.dist-info}/WHEEL +1 -1
- jolt/plugins/amqp.py +0 -855
- jolt/plugins/debian.py +0 -338
- jolt/plugins/repo.py +0 -253
- jolt/plugins/snap.py +0 -122
- jolt-0.9.172.dist-info/RECORD +0 -92
- {jolt-0.9.172.dist-info → jolt-0.9.435.dist-info}/entry_points.txt +0 -0
- {jolt-0.9.172.dist-info → jolt-0.9.435.dist-info}/top_level.txt +0 -0
jolt/tasks.py
CHANGED
|
@@ -10,8 +10,10 @@ import platform
|
|
|
10
10
|
from threading import RLock
|
|
11
11
|
import subprocess
|
|
12
12
|
from os import environ
|
|
13
|
+
from os import sys as os_sys
|
|
13
14
|
import sys
|
|
14
15
|
import unittest as ut
|
|
16
|
+
from urllib.parse import urlparse
|
|
15
17
|
import uuid
|
|
16
18
|
import re
|
|
17
19
|
import traceback
|
|
@@ -20,12 +22,13 @@ from jolt import filesystem as fs
|
|
|
20
22
|
from jolt import log
|
|
21
23
|
from jolt import utils
|
|
22
24
|
from jolt.cache import ArtifactAttributeSetProvider
|
|
23
|
-
from jolt.error import
|
|
25
|
+
from jolt.error import raise_error_if, raise_task_error, raise_task_error_if
|
|
24
26
|
from jolt.error import raise_unreported_task_error_if
|
|
25
|
-
from jolt.error import JoltError, JoltCommandError
|
|
27
|
+
from jolt.error import JoltError, JoltCommandError, LoggedJoltError
|
|
26
28
|
from jolt.expires import Immediately
|
|
27
29
|
from jolt.influence import FileInfluence, TaintInfluenceProvider
|
|
28
30
|
from jolt.influence import TaskClassSourceInfluence
|
|
31
|
+
from jolt.influence import CallbackInfluence
|
|
29
32
|
from jolt.influence import attribute as attribute_influence
|
|
30
33
|
from jolt.influence import environ as environ_influence
|
|
31
34
|
from jolt.influence import source as source_influence
|
|
@@ -134,7 +137,7 @@ class Parameter(object):
|
|
|
134
137
|
self.name = name
|
|
135
138
|
|
|
136
139
|
def __init__(self, default=None, values=None, required=True,
|
|
137
|
-
const=False, influence=True, help=None):
|
|
140
|
+
const=False, influence=True, help=None, valuesfn=None):
|
|
138
141
|
"""
|
|
139
142
|
Creates a new parameter.
|
|
140
143
|
|
|
@@ -142,6 +145,10 @@ class Parameter(object):
|
|
|
142
145
|
default (str, optional): An optional default value.
|
|
143
146
|
values (list, optional): A list of accepted values. An
|
|
144
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.
|
|
145
152
|
required (boolean, optional): If required, the parameter must be assigned
|
|
146
153
|
a value before the task can be executed. The default is ``True``.
|
|
147
154
|
const (boolean, optional): If const is True, the parameter is immutable
|
|
@@ -163,6 +170,7 @@ class Parameter(object):
|
|
|
163
170
|
self._default = default
|
|
164
171
|
self._value = default
|
|
165
172
|
self._accepted_values = values
|
|
173
|
+
self._accepted_values_fn = valuesfn
|
|
166
174
|
self._required = required
|
|
167
175
|
self._const = const
|
|
168
176
|
self._influence = influence
|
|
@@ -189,7 +197,7 @@ class Parameter(object):
|
|
|
189
197
|
def highlight(value):
|
|
190
198
|
return colors.bright(value) if self._is_default(value) else colors.dim(value)
|
|
191
199
|
|
|
192
|
-
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 ""
|
|
193
201
|
|
|
194
202
|
def __str__(self):
|
|
195
203
|
""" Returns the parameter value as a string """
|
|
@@ -198,6 +206,8 @@ class Parameter(object):
|
|
|
198
206
|
def _validate(self, value, what=None):
|
|
199
207
|
if self._accepted_values is not None and value not in self._accepted_values:
|
|
200
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)
|
|
201
211
|
|
|
202
212
|
def get_default(self):
|
|
203
213
|
""" Get the default value of the parameter.
|
|
@@ -435,7 +445,7 @@ class IntParameter(Parameter):
|
|
|
435
445
|
"""
|
|
436
446
|
|
|
437
447
|
def __init__(self, default=None, min=None, max=None, values=None, required=True, const=False,
|
|
438
|
-
influence=True, help=None):
|
|
448
|
+
influence=True, help=None, valuesfn=None):
|
|
439
449
|
"""
|
|
440
450
|
Creates a new parameter.
|
|
441
451
|
|
|
@@ -483,7 +493,8 @@ class IntParameter(Parameter):
|
|
|
483
493
|
required=required,
|
|
484
494
|
const=const,
|
|
485
495
|
influence=influence,
|
|
486
|
-
help=help
|
|
496
|
+
help=help,
|
|
497
|
+
valuesfn=valuesfn)
|
|
487
498
|
|
|
488
499
|
def _validate(self, value, what=None):
|
|
489
500
|
if self._min is not None and value < self._min:
|
|
@@ -687,6 +698,10 @@ class ListParameter(Parameter):
|
|
|
687
698
|
for item in value:
|
|
688
699
|
if item not in self._accepted_values:
|
|
689
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)
|
|
690
705
|
|
|
691
706
|
def get_value(self):
|
|
692
707
|
return "+".join(self._value)
|
|
@@ -768,6 +783,7 @@ class TaskRegistry(object):
|
|
|
768
783
|
self.env = env
|
|
769
784
|
self.tasks = {}
|
|
770
785
|
self.instances = {}
|
|
786
|
+
self._workspace_resources = []
|
|
771
787
|
|
|
772
788
|
@staticmethod
|
|
773
789
|
def get(*args, **kwargs):
|
|
@@ -776,6 +792,21 @@ class TaskRegistry(object):
|
|
|
776
792
|
return TaskRegistry._instance
|
|
777
793
|
|
|
778
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
|
+
|
|
779
810
|
self.tasks[cls.name] = cls
|
|
780
811
|
|
|
781
812
|
def add_task(self, task, extra_params):
|
|
@@ -784,6 +815,13 @@ class TaskRegistry(object):
|
|
|
784
815
|
full_name = utils.format_task_name(name, params)
|
|
785
816
|
self.instances[full_name] = task
|
|
786
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
|
+
|
|
787
825
|
def get_task_class(self, name):
|
|
788
826
|
return self.tasks.get(name)
|
|
789
827
|
|
|
@@ -803,12 +841,17 @@ class TaskRegistry(object):
|
|
|
803
841
|
if cls:
|
|
804
842
|
task = cls(parameters=params, manifest=manifest, buildenv=buildenv)
|
|
805
843
|
task = self.instances.get(task.qualified_name, task)
|
|
806
|
-
|
|
807
|
-
|
|
844
|
+
if not isinstance(task, Resource) or isinstance(task, WorkspaceResource):
|
|
845
|
+
self.instances[task.qualified_name] = task
|
|
846
|
+
self.instances[full_name] = task
|
|
808
847
|
return task
|
|
809
848
|
|
|
810
849
|
raise_task_error_if(not task, full_name, "No such task")
|
|
811
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
|
+
|
|
812
855
|
def set_default_parameters(self, task):
|
|
813
856
|
name, params = utils.parse_task_name(task)
|
|
814
857
|
|
|
@@ -880,6 +923,14 @@ class TaskGenerator(object):
|
|
|
880
923
|
|
|
881
924
|
|
|
882
925
|
class attributes:
|
|
926
|
+
@staticmethod
|
|
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
|
+
|
|
883
934
|
@staticmethod
|
|
884
935
|
def artifact(name, session=False):
|
|
885
936
|
"""Decorator adding an additional artifact to a task.
|
|
@@ -916,7 +967,7 @@ class attributes:
|
|
|
916
967
|
@functools.wraps(cls._artifacts)
|
|
917
968
|
def _artifacts(self, cache, node):
|
|
918
969
|
artifacts = _old_artifacts(self, cache, node)
|
|
919
|
-
artifacts += [cache.get_artifact(node, name, session=session)]
|
|
970
|
+
artifacts += [cache.get_artifact(node, name, session=session or isinstance(cls, Resource))]
|
|
920
971
|
return artifacts
|
|
921
972
|
|
|
922
973
|
cls._artifacts = _artifacts
|
|
@@ -924,6 +975,145 @@ class attributes:
|
|
|
924
975
|
|
|
925
976
|
return decorate
|
|
926
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.
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
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
|
|
1030
|
+
"""
|
|
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
|
|
1116
|
+
|
|
927
1117
|
@staticmethod
|
|
928
1118
|
def attribute(alias, target, influence=True, default=False):
|
|
929
1119
|
"""
|
|
@@ -1026,6 +1216,65 @@ class attributes:
|
|
|
1026
1216
|
return utils.concat_attributes("_publish_files", attrib)(cls)
|
|
1027
1217
|
return decorate
|
|
1028
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
|
+
|
|
1029
1278
|
@staticmethod
|
|
1030
1279
|
def requires(attrib):
|
|
1031
1280
|
"""
|
|
@@ -1112,7 +1361,7 @@ class attributes:
|
|
|
1112
1361
|
return _decorate
|
|
1113
1362
|
|
|
1114
1363
|
@staticmethod
|
|
1115
|
-
def platform(
|
|
1364
|
+
def platform(attrib):
|
|
1116
1365
|
"""
|
|
1117
1366
|
Decorates a task with an alternative ``platform`` attribute.
|
|
1118
1367
|
|
|
@@ -1136,6 +1385,40 @@ class attributes:
|
|
|
1136
1385
|
cls.system = property(lambda t: t._system.value)
|
|
1137
1386
|
return cls
|
|
1138
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
|
+
|
|
1139
1422
|
|
|
1140
1423
|
class TaskBase(object):
|
|
1141
1424
|
""" Task base class. """
|
|
@@ -1219,6 +1502,9 @@ class TaskBase(object):
|
|
|
1219
1502
|
joltproject = None
|
|
1220
1503
|
""" Name of project this task belongs to. """
|
|
1221
1504
|
|
|
1505
|
+
local = False
|
|
1506
|
+
""" A local task is only executed on the local node where the build is initiated. """
|
|
1507
|
+
|
|
1222
1508
|
name = None
|
|
1223
1509
|
""" Name of the task. Derived from class name if not set. """
|
|
1224
1510
|
|
|
@@ -1293,6 +1579,14 @@ class TaskBase(object):
|
|
|
1293
1579
|
execution requests, the instance ID won't be.
|
|
1294
1580
|
"""
|
|
1295
1581
|
|
|
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
|
+
|
|
1296
1590
|
def __init__(self, parameters=None, manifest=None, buildenv=None, **kwargs):
|
|
1297
1591
|
self._identity = None
|
|
1298
1592
|
self._report = _JoltTask()
|
|
@@ -1334,7 +1628,7 @@ class TaskBase(object):
|
|
|
1334
1628
|
def _apply_protobuf(self, buildenv):
|
|
1335
1629
|
if buildenv is None:
|
|
1336
1630
|
return
|
|
1337
|
-
task = buildenv.tasks.get(self.
|
|
1631
|
+
task = buildenv.tasks.get(self.exported_name)
|
|
1338
1632
|
if not task:
|
|
1339
1633
|
return
|
|
1340
1634
|
if task.identity:
|
|
@@ -1410,6 +1704,9 @@ class TaskBase(object):
|
|
|
1410
1704
|
"Required parameter '{0}' has not been set", key)
|
|
1411
1705
|
|
|
1412
1706
|
def _verify_influence(self, deps, artifact, tools, sources=None):
|
|
1707
|
+
if "configurationrepository" in self.name:
|
|
1708
|
+
breakpoint()
|
|
1709
|
+
|
|
1413
1710
|
# Verify that any transformed sources are influencing
|
|
1414
1711
|
sources = set(map(tools.expand_path, sources or []))
|
|
1415
1712
|
|
|
@@ -1426,29 +1723,29 @@ class TaskBase(object):
|
|
|
1426
1723
|
return not fs.is_relative_to(fname, rootpath)
|
|
1427
1724
|
return _filter
|
|
1428
1725
|
|
|
1726
|
+
# Ignore any files in build directories
|
|
1727
|
+
sources = filter(_subpath_filter(tools.expand_path(tools.buildroot)), sources)
|
|
1728
|
+
|
|
1429
1729
|
for _, dep in deps.items():
|
|
1430
1730
|
deptask = dep.task
|
|
1431
1731
|
if isinstance(deptask, FileInfluence):
|
|
1432
1732
|
# Resource dependencies may cover the influence implicitly
|
|
1433
1733
|
deppath = self.tools.expand_path(str(deptask.path))
|
|
1434
|
-
sources =
|
|
1734
|
+
sources = filter(lambda d, dt=deptask: not dt.is_influenced_by(self, d), sources)
|
|
1435
1735
|
else:
|
|
1436
1736
|
# Ignore any files in artifacts
|
|
1437
1737
|
deppath = self.tools.expand_path(dep.path)
|
|
1438
|
-
sources =
|
|
1439
|
-
|
|
1440
|
-
# Ignore any files in build directories
|
|
1441
|
-
sources = filter(_subpath_filter(tools.expand_path(tools.buildroot)), sources)
|
|
1442
|
-
sources = set(sources)
|
|
1738
|
+
sources = filter(_subpath_filter(deppath), sources)
|
|
1443
1739
|
|
|
1444
1740
|
for ip in self.influence:
|
|
1445
1741
|
if not isinstance(ip, FileInfluence):
|
|
1446
1742
|
continue
|
|
1447
|
-
|
|
1448
|
-
|
|
1743
|
+
sources = {source for source in sources if not ip.is_influenced_by(self, source)}
|
|
1744
|
+
|
|
1449
1745
|
for source in sources:
|
|
1450
1746
|
log.warning("Missing influence: {} ({})", source, self.name)
|
|
1451
|
-
|
|
1747
|
+
|
|
1748
|
+
raise_task_error_if(set(sources), self, "Task is missing source influence")
|
|
1452
1749
|
|
|
1453
1750
|
def _get_export_objects(self):
|
|
1454
1751
|
return self._exports
|
|
@@ -1489,6 +1786,16 @@ class TaskBase(object):
|
|
|
1489
1786
|
self.name,
|
|
1490
1787
|
self._get_explicitly_set_parameters())
|
|
1491
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
|
+
|
|
1492
1799
|
def expand(self, string_or_list, *args, **kwargs):
|
|
1493
1800
|
""" Expands keyword arguments/macros in a format string.
|
|
1494
1801
|
|
|
@@ -2353,7 +2660,7 @@ class Runner(Task):
|
|
|
2353
2660
|
for task, artifact in deps.items():
|
|
2354
2661
|
if not artifact.task.is_cacheable():
|
|
2355
2662
|
continue
|
|
2356
|
-
if artifact.strings.executable
|
|
2663
|
+
if artifact.strings.executable is None:
|
|
2357
2664
|
self.verbose("No executable found in task artifact for '{}'", task)
|
|
2358
2665
|
continue
|
|
2359
2666
|
with tools.cwd(artifact.path):
|
|
@@ -2376,6 +2683,10 @@ class ErrorProxy(object):
|
|
|
2376
2683
|
def type(self):
|
|
2377
2684
|
return self._error.type
|
|
2378
2685
|
|
|
2686
|
+
@type.setter
|
|
2687
|
+
def type(self, value):
|
|
2688
|
+
self._error.type = value
|
|
2689
|
+
|
|
2379
2690
|
@property
|
|
2380
2691
|
def details(self):
|
|
2381
2692
|
return self._error.details
|
|
@@ -2391,11 +2702,21 @@ class ErrorProxy(object):
|
|
|
2391
2702
|
|
|
2392
2703
|
class ReportProxy(object):
|
|
2393
2704
|
def __init__(self, task, report):
|
|
2705
|
+
from jolt import config
|
|
2394
2706
|
self._task = task
|
|
2395
2707
|
self._report = report
|
|
2708
|
+
self._max_errors = config.getint("jolt", "task_max_errors", 100)
|
|
2396
2709
|
|
|
2397
2710
|
def add_error(self, type, location, message, details=""):
|
|
2398
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
|
+
|
|
2399
2720
|
error = self._report.create_error()
|
|
2400
2721
|
error.type = type
|
|
2401
2722
|
error.location = location
|
|
@@ -2416,11 +2737,8 @@ class ReportProxy(object):
|
|
|
2416
2737
|
"""
|
|
2417
2738
|
for match in re.finditer(regex, logbuf, re.MULTILINE):
|
|
2418
2739
|
error = match.groupdict()
|
|
2419
|
-
self.add_error(
|
|
2420
|
-
|
|
2421
|
-
error.get("location", ""),
|
|
2422
|
-
error.get("message", ""),
|
|
2423
|
-
error.get("details", ""))
|
|
2740
|
+
if not self.add_error(type, error.get("location", ""), error.get("message", ""), error.get("details", "")):
|
|
2741
|
+
break
|
|
2424
2742
|
|
|
2425
2743
|
def add_regex_errors_with_file(self, type, regex, logbuf, reldir, filterfn=lambda n: True):
|
|
2426
2744
|
"""
|
|
@@ -2441,25 +2759,29 @@ class ReportProxy(object):
|
|
|
2441
2759
|
if not filterfn(error):
|
|
2442
2760
|
continue
|
|
2443
2761
|
if error["location"] not in errors_by_location:
|
|
2444
|
-
errors_by_location[error["location"]] = (error, [error["message"]])
|
|
2762
|
+
errors_by_location[error["location"]] = (error, [error["message"]], error.get("details", ""))
|
|
2445
2763
|
else:
|
|
2446
2764
|
errors_by_location[error["location"]][1].append(error["message"])
|
|
2447
2765
|
|
|
2448
|
-
for error, msgs in errors_by_location.values():
|
|
2766
|
+
for error, msgs, details in errors_by_location.values():
|
|
2449
2767
|
message = "\n".join(utils.unique_list(msgs))
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
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 = ""
|
|
2457
2776
|
|
|
2458
2777
|
location = error.get("location", "")
|
|
2459
2778
|
if location:
|
|
2460
|
-
|
|
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)
|
|
2461
2782
|
|
|
2462
|
-
self.add_error(type, location, message, details)
|
|
2783
|
+
if not self.add_error(type, location, message, details):
|
|
2784
|
+
break
|
|
2463
2785
|
|
|
2464
2786
|
def add_exception(self, exc, errtype=None, location=None):
|
|
2465
2787
|
"""
|
|
@@ -2498,10 +2820,26 @@ class ReportProxy(object):
|
|
|
2498
2820
|
def errors(self):
|
|
2499
2821
|
return [ErrorProxy(error) for error in self._report.errors]
|
|
2500
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
|
+
|
|
2501
2830
|
@property
|
|
2502
2831
|
def manifest(self):
|
|
2503
2832
|
return self._report
|
|
2504
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
|
+
|
|
2505
2843
|
|
|
2506
2844
|
class Resource(Task):
|
|
2507
2845
|
"""
|
|
@@ -2515,14 +2853,18 @@ class Resource(Task):
|
|
|
2515
2853
|
|
|
2516
2854
|
"""
|
|
2517
2855
|
|
|
2518
|
-
cacheable = False
|
|
2519
|
-
|
|
2520
2856
|
abstract = True
|
|
2521
2857
|
""" An abstract resource class indended to be subclassed. """
|
|
2522
2858
|
|
|
2859
|
+
release_on_error = False
|
|
2860
|
+
""" Call release if an exception occurs during acquire. """
|
|
2861
|
+
|
|
2523
2862
|
def __init__(self, *args, **kwargs):
|
|
2524
2863
|
super().__init__(*args, **kwargs)
|
|
2525
2864
|
|
|
2865
|
+
def _artifacts(self, cache, node):
|
|
2866
|
+
return [cache.get_artifact(node, "main", session=True)]
|
|
2867
|
+
|
|
2526
2868
|
def is_runnable(self):
|
|
2527
2869
|
return False
|
|
2528
2870
|
|
|
@@ -2586,13 +2928,21 @@ class WorkspaceResource(Resource):
|
|
|
2586
2928
|
raise_task_error_if(len(self.requires) > 0, self,
|
|
2587
2929
|
"Workspace resource is not allowed to have requirements")
|
|
2588
2930
|
|
|
2589
|
-
def acquire(self, **kwargs):
|
|
2931
|
+
def acquire(self, *args, **kwargs):
|
|
2590
2932
|
return self.acquire_ws()
|
|
2591
2933
|
|
|
2592
|
-
def release(self, **kwargs):
|
|
2934
|
+
def release(self, *args, **kwargs):
|
|
2593
2935
|
return self.release_ws()
|
|
2594
2936
|
|
|
2595
|
-
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):
|
|
2596
2946
|
""" Called to acquire the resource.
|
|
2597
2947
|
|
|
2598
2948
|
An implementor overrides this method in a subclass. The acquired
|
|
@@ -2696,6 +3046,7 @@ class Download(Task):
|
|
|
2696
3046
|
Once downloaded, archives are extracted and all of their files are published.
|
|
2697
3047
|
If the file is not an archive it is published as is. Recognized archive extensions are:
|
|
2698
3048
|
|
|
3049
|
+
- .7z
|
|
2699
3050
|
- .tar
|
|
2700
3051
|
- .tar.bz2
|
|
2701
3052
|
- .tar.gz
|
|
@@ -2758,7 +3109,7 @@ class Download(Task):
|
|
|
2758
3109
|
return fs.posixpath.basename(url.path) or "file"
|
|
2759
3110
|
|
|
2760
3111
|
def run(self, deps, tools):
|
|
2761
|
-
supported_formats = [".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
|
|
3112
|
+
supported_formats = [".7z", ".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
|
|
2762
3113
|
|
|
2763
3114
|
raise_task_error_if(not self.url, self, "No URL(s) specified")
|
|
2764
3115
|
|
|
@@ -2855,7 +3206,8 @@ class Script(Task):
|
|
|
2855
3206
|
doc = self.__doc__.split("---", 1)
|
|
2856
3207
|
script = doc[1] if len(doc) > 1 else doc[0]
|
|
2857
3208
|
script = script.splitlines()
|
|
2858
|
-
|
|
3209
|
+
if os_sys.version_info < (3, 13):
|
|
3210
|
+
script = [line[4:] for line in script]
|
|
2859
3211
|
script = "\n".join(script)
|
|
2860
3212
|
script = script.lstrip()
|
|
2861
3213
|
if not script.startswith("#!"):
|
|
@@ -2982,7 +3334,7 @@ class Test(Task):
|
|
|
2982
3334
|
Abstract test tasks can't be executed and won't be listed.
|
|
2983
3335
|
"""
|
|
2984
3336
|
|
|
2985
|
-
|
|
3337
|
+
filter = Parameter(required=False, help="Test-case filter wildcard.")
|
|
2986
3338
|
|
|
2987
3339
|
def __init__(self, *args, **kwargs):
|
|
2988
3340
|
super().__init__(*args, **kwargs)
|
|
@@ -3011,24 +3363,21 @@ class Test(Task):
|
|
|
3011
3363
|
"""
|
|
3012
3364
|
raise_error_if(type(args) is not list, "Test.parameterized() expects a list as argument")
|
|
3013
3365
|
|
|
3014
|
-
|
|
3015
|
-
def
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
retval = super().__get__(obj, cls)
|
|
3021
|
-
retval.__name__ = f"{self.func.__name__}[{self.__index}]"
|
|
3022
|
-
retval.__doc__ = self.func.__doc__
|
|
3023
|
-
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
|
|
3024
3372
|
|
|
3025
3373
|
def decorate(method):
|
|
3026
3374
|
frame = sys._getframe().f_back.f_locals
|
|
3027
3375
|
for index, arg in enumerate(args):
|
|
3028
|
-
testmethod =
|
|
3376
|
+
testmethod = make_method(index, method, *utils.as_list(arg))
|
|
3029
3377
|
name = f"{method.__name__}[{index}]"
|
|
3030
3378
|
frame[name] = testmethod
|
|
3031
3379
|
return None
|
|
3380
|
+
|
|
3032
3381
|
return decorate
|
|
3033
3382
|
|
|
3034
3383
|
def setup(self, deps, tools):
|
|
@@ -3137,7 +3486,7 @@ class Test(Task):
|
|
|
3137
3486
|
def run(self, deps, tools):
|
|
3138
3487
|
testsuite = ut.TestSuite()
|
|
3139
3488
|
for test in self._get_test_names():
|
|
3140
|
-
if self.
|
|
3489
|
+
if self.filter.is_unset() or fnmatch.fnmatch(test, str(self.filter)):
|
|
3141
3490
|
testfunc = getattr(self, test)
|
|
3142
3491
|
if not testfunc:
|
|
3143
3492
|
continue
|
|
@@ -3158,7 +3507,7 @@ class Test(Task):
|
|
|
3158
3507
|
|
|
3159
3508
|
|
|
3160
3509
|
@ArtifactAttributeSetProvider.Register
|
|
3161
|
-
class
|
|
3510
|
+
class WorkspaceResourceAttributeSetProvider(ArtifactAttributeSetProvider):
|
|
3162
3511
|
def create(self, artifact):
|
|
3163
3512
|
pass
|
|
3164
3513
|
|
|
@@ -3170,63 +3519,30 @@ class ResourceAttributeSetProvider(ArtifactAttributeSetProvider):
|
|
|
3170
3519
|
|
|
3171
3520
|
def apply(self, task, artifact):
|
|
3172
3521
|
resource = artifact.task
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
if not hasattr(resource, "_run_env"):
|
|
3177
|
-
raise_error("Internal scheduling error, resource has not been prepared: {}", task.short_qualified_name)
|
|
3522
|
+
node = artifact.get_node()
|
|
3523
|
+
if not node.is_workspace_resource():
|
|
3524
|
+
return
|
|
3178
3525
|
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
sig = signature(resource.acquire)
|
|
3182
|
-
try:
|
|
3183
|
-
ba = sig.bind_partial(artifact=artifact, deps=deps, tools=resource.tools, owner=task)
|
|
3184
|
-
acquire = resource.acquire
|
|
3185
|
-
except Exception:
|
|
3186
|
-
ba = sig.bind_partial(artifact, deps, resource.tools)
|
|
3187
|
-
acquire = utils.deprecated(resource.acquire)
|
|
3526
|
+
resource.deps = node.cache.get_context(node)
|
|
3527
|
+
resource.deps.__enter__()
|
|
3188
3528
|
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
log.info(colors.green("Resource acquisition finished after {} ({})"),
|
|
3197
|
-
ts, resource.short_qualified_name)
|
|
3198
|
-
except Exception as e:
|
|
3199
|
-
if not isinstance(resource, WorkspaceResource):
|
|
3200
|
-
log.error("Resource acquisition failed after {} ({})",
|
|
3201
|
-
ts, resource.short_qualified_name)
|
|
3202
|
-
raise e
|
|
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
|
|
3203
3536
|
|
|
3204
3537
|
def unapply(self, task, artifact):
|
|
3205
3538
|
resource = artifact.task
|
|
3206
|
-
|
|
3207
|
-
|
|
3539
|
+
node = artifact.get_node()
|
|
3540
|
+
if not node.is_workspace_resource():
|
|
3541
|
+
return
|
|
3208
3542
|
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
release = resource.release
|
|
3214
|
-
except Exception:
|
|
3215
|
-
ba = sig.bind_partial(artifact, deps, resource.tools)
|
|
3216
|
-
release = utils.deprecated(resource.release)
|
|
3543
|
+
try:
|
|
3544
|
+
resource.release(artifact=artifact, deps=resource.deps, tools=resource.tools, owner=task)
|
|
3545
|
+
except Exception as e:
|
|
3546
|
+
raise e
|
|
3217
3547
|
|
|
3218
|
-
|
|
3219
|
-
if not isinstance(resource, WorkspaceResource):
|
|
3220
|
-
ts = utils.duration()
|
|
3221
|
-
log.info(colors.blue("Resource release started ({})"),
|
|
3222
|
-
resource.short_qualified_name)
|
|
3223
|
-
release(*ba.args, **ba.kwargs)
|
|
3224
|
-
if not isinstance(resource, WorkspaceResource):
|
|
3225
|
-
log.info(colors.green("Resource release finished after {} ({})"),
|
|
3226
|
-
ts, resource.short_qualified_name)
|
|
3227
|
-
except Exception as e:
|
|
3228
|
-
if not isinstance(resource, WorkspaceResource):
|
|
3229
|
-
log.error("Resource release failed after {} ({})",
|
|
3230
|
-
ts, resource.short_qualified_name)
|
|
3231
|
-
raise e
|
|
3232
|
-
deps.__exit__(None, None, None)
|
|
3548
|
+
resource.deps.__exit__(None, None, None)
|