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/loader.py
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
import fasteners
|
|
3
|
+
import json
|
|
1
4
|
import glob
|
|
2
5
|
from importlib.machinery import SourceFileLoader
|
|
3
6
|
import os
|
|
7
|
+
import platform
|
|
8
|
+
import subprocess
|
|
4
9
|
import sys
|
|
5
10
|
from types import ModuleType
|
|
6
11
|
|
|
7
12
|
from jolt import inspection
|
|
8
|
-
from jolt.tasks import
|
|
9
|
-
from jolt.
|
|
10
|
-
from jolt.error import raise_error_if, raise_task_error_if
|
|
13
|
+
from jolt.tasks import Task, TaskGenerator
|
|
14
|
+
from jolt.error import raise_error_if
|
|
11
15
|
from jolt import common_pb2 as common_pb
|
|
12
16
|
from jolt import config
|
|
13
17
|
from jolt import filesystem as fs
|
|
14
18
|
from jolt import log
|
|
15
19
|
from jolt import utils
|
|
16
|
-
from jolt.
|
|
17
|
-
from jolt.manifest import ManifestExtensionRegistry
|
|
20
|
+
from jolt.tools import Tools
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class Recipe(object):
|
|
24
|
+
"""
|
|
25
|
+
Abstract representation a single recipe file.
|
|
26
|
+
|
|
27
|
+
Implementations of this class are responsible for reading the recipe source
|
|
28
|
+
from the filesystem. The recipe source is then parsed and the tasks are
|
|
29
|
+
extracted and made available for execution.
|
|
30
|
+
|
|
31
|
+
The format of the recipe source is implementation defined.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
tasks = []
|
|
35
|
+
"""
|
|
36
|
+
List of task classes defined in the recipe.
|
|
37
|
+
|
|
38
|
+
Available after the recipe has been loaded.
|
|
39
|
+
"""
|
|
40
|
+
|
|
21
41
|
def __init__(self, path, joltdir=None, project=None, source=None):
|
|
22
42
|
self.path = path
|
|
23
43
|
self.basepath = os.path.basename(path)
|
|
@@ -27,12 +47,14 @@ class Recipe(object):
|
|
|
27
47
|
self.tasks = []
|
|
28
48
|
|
|
29
49
|
def load(self):
|
|
50
|
+
""" Load the recipe source from the file system. """
|
|
30
51
|
raise_error_if(self.source is not None, "recipe already loaded: {}", self.path)
|
|
31
52
|
|
|
32
53
|
with open(self.path) as f:
|
|
33
54
|
self.source = f.read()
|
|
34
55
|
|
|
35
56
|
def save(self):
|
|
57
|
+
""" Save the recipe source to the file system. """
|
|
36
58
|
raise_error_if(self.source is None, "recipe source unknown: {}", self.path)
|
|
37
59
|
|
|
38
60
|
with open(self.path, "w") as f:
|
|
@@ -40,6 +62,8 @@ class Recipe(object):
|
|
|
40
62
|
|
|
41
63
|
|
|
42
64
|
class NativeRecipe(Recipe):
|
|
65
|
+
""" Represents a Python recipe file (.jolt, .py). """
|
|
66
|
+
|
|
43
67
|
@staticmethod
|
|
44
68
|
def _is_abstract(cls):
|
|
45
69
|
return cls.__dict__.get("abstract", False) or cls.__name__.startswith("_")
|
|
@@ -50,7 +74,14 @@ class NativeRecipe(Recipe):
|
|
|
50
74
|
issubclass(cls, Task) and \
|
|
51
75
|
not NativeRecipe._is_abstract(cls)
|
|
52
76
|
|
|
53
|
-
def load(self):
|
|
77
|
+
def load(self, joltdir=None):
|
|
78
|
+
"""
|
|
79
|
+
Load the recipe source from the file system.
|
|
80
|
+
|
|
81
|
+
Python classes defined in the recipe source are extracted and made available
|
|
82
|
+
as tasks for execution. Task classes must be subclasses of Task or TaskGenerator.
|
|
83
|
+
|
|
84
|
+
"""
|
|
54
85
|
super(NativeRecipe, self).load()
|
|
55
86
|
|
|
56
87
|
name = utils.canonical(self.path)
|
|
@@ -64,7 +95,7 @@ class NativeRecipe(Recipe):
|
|
|
64
95
|
generators = []
|
|
65
96
|
|
|
66
97
|
for cls in classes[TaskGenerator]:
|
|
67
|
-
cls.joltdir = os.path.normpath(self.joltdir or os.path.dirname(self.path))
|
|
98
|
+
cls.joltdir = os.path.normpath(joltdir or self.joltdir or os.path.dirname(self.path))
|
|
68
99
|
generators.append(cls())
|
|
69
100
|
|
|
70
101
|
for generator in generators:
|
|
@@ -73,7 +104,7 @@ class NativeRecipe(Recipe):
|
|
|
73
104
|
|
|
74
105
|
for task in classes[Task]:
|
|
75
106
|
task.name = task.name or task.__name__.lower()
|
|
76
|
-
task.joltdir = os.path.normpath(self.joltdir or os.path.dirname(self.path))
|
|
107
|
+
task.joltdir = os.path.normpath(joltdir or self.joltdir or os.path.dirname(self.path))
|
|
77
108
|
task.joltproject = self.project
|
|
78
109
|
self.tasks.append(task)
|
|
79
110
|
|
|
@@ -81,25 +112,62 @@ class NativeRecipe(Recipe):
|
|
|
81
112
|
|
|
82
113
|
|
|
83
114
|
class Loader(object):
|
|
84
|
-
|
|
115
|
+
"""
|
|
116
|
+
Base class for recipe loaders.
|
|
117
|
+
|
|
118
|
+
A Loader is responsible for finding recipes in the file system providing a list
|
|
119
|
+
of Recipe:s from which tasks can be loaded.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def recipes(self) -> list:
|
|
123
|
+
""" Return a list of Recipe:s from which tasks can be loaded. """
|
|
85
124
|
pass
|
|
86
125
|
|
|
87
126
|
|
|
88
127
|
class LoaderFactory(object):
|
|
128
|
+
"""
|
|
129
|
+
A factory for creating Loader instances.
|
|
130
|
+
|
|
131
|
+
Factories are registered with the JoltLoader where it is used to create Loader instances.
|
|
132
|
+
"""
|
|
89
133
|
def create(self):
|
|
90
134
|
raise NotImplementedError()
|
|
91
135
|
|
|
92
136
|
|
|
93
137
|
class NativeLoader(Loader):
|
|
138
|
+
""" A loader for Python recipe files (.jolt, .py). """
|
|
139
|
+
|
|
94
140
|
def __init__(self, searchpath):
|
|
95
|
-
|
|
96
|
-
|
|
141
|
+
"""
|
|
142
|
+
Create a new NativeLoader instance.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
searchpath (str): The path to search for recipe files. If the path is a file,
|
|
146
|
+
only that file will be loaded. If the path is a directory, all files with
|
|
147
|
+
the .jolt or .py extension will be loaded.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
self._files = self._find_files(searchpath)
|
|
152
|
+
self._recipes = self._load_files(self._files) if self._files else []
|
|
97
153
|
|
|
98
154
|
def _find_files(self, searchpath):
|
|
99
|
-
|
|
155
|
+
# If the searchpath is a file, load it directly
|
|
156
|
+
if fs.path.isdir(searchpath):
|
|
157
|
+
return glob.glob(fs.path.join(searchpath, "*.jolt"))
|
|
158
|
+
|
|
159
|
+
_, ext = fs.path.splitext(searchpath)
|
|
160
|
+
raise_error_if(not fs.path.exists(searchpath), "File does not exist: {}", searchpath)
|
|
161
|
+
raise_error_if(ext not in [".build", ".jolt", ".py"], "Invalid file extension: {}", ext)
|
|
162
|
+
|
|
163
|
+
return [searchpath]
|
|
164
|
+
|
|
165
|
+
def _load_files(self, files):
|
|
166
|
+
recipes = []
|
|
100
167
|
for filepath in files:
|
|
101
168
|
recipe = NativeRecipe(filepath)
|
|
102
|
-
|
|
169
|
+
recipes.append(recipe)
|
|
170
|
+
return recipes
|
|
103
171
|
|
|
104
172
|
@property
|
|
105
173
|
def recipes(self):
|
|
@@ -110,6 +178,7 @@ _loaders = []
|
|
|
110
178
|
|
|
111
179
|
|
|
112
180
|
def register(factory):
|
|
181
|
+
""" Register a LoaderFactory with the JoltLoader. """
|
|
113
182
|
raise_error_if(not issubclass(factory, LoaderFactory),
|
|
114
183
|
"{} is not a LoaderFactory", factory.__name__)
|
|
115
184
|
_loaders.append(factory)
|
|
@@ -117,64 +186,38 @@ def register(factory):
|
|
|
117
186
|
|
|
118
187
|
@register
|
|
119
188
|
class NativeLoaderFactory(LoaderFactory):
|
|
189
|
+
""" A factory for creating NativeLoader instances. """
|
|
120
190
|
def create(self, searchpath):
|
|
121
191
|
return NativeLoader(searchpath)
|
|
122
192
|
|
|
123
193
|
|
|
124
194
|
@utils.Singleton
|
|
125
195
|
class JoltLoader(object):
|
|
196
|
+
"""
|
|
197
|
+
The JoltLoader is responsible for loading recipes from the file system.
|
|
198
|
+
|
|
199
|
+
The JoltLoader is a singleton and is used to load recipes from the file system.
|
|
200
|
+
The recipes are loaded from the workspace directory and any project directories
|
|
201
|
+
defined in the workspace. The recipes are then made available for execution.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
|
|
126
205
|
filename = "*.jolt"
|
|
127
206
|
|
|
128
207
|
def __init__(self):
|
|
208
|
+
self._lock = None
|
|
129
209
|
self._recipes = []
|
|
130
210
|
self._tasks = []
|
|
131
211
|
self._path = None
|
|
132
|
-
self.
|
|
133
|
-
self.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self._project_modules[project] = modules
|
|
140
|
-
|
|
141
|
-
def _get_project_modules(self, project):
|
|
142
|
-
return self._project_modules.get(project, [])
|
|
143
|
-
|
|
144
|
-
def _add_project_recipe(self, project, joltdir, src):
|
|
145
|
-
recipes = self._project_recipes.get(project, [])
|
|
146
|
-
recipes.append((joltdir, src))
|
|
147
|
-
self._project_recipes[project] = recipes
|
|
148
|
-
|
|
149
|
-
def _get_project_recipes(self, project):
|
|
150
|
-
return self._project_recipes.get(project, [])
|
|
151
|
-
|
|
152
|
-
def _add_project_resource(self, project, resource_name, resource_task):
|
|
153
|
-
class ProjectResource(Alias):
|
|
154
|
-
name = project + "/" + resource_name
|
|
155
|
-
requires = [resource_task]
|
|
156
|
-
|
|
157
|
-
self._tasks.append(ProjectResource)
|
|
158
|
-
resources = self._project_resources.get(project, [])
|
|
159
|
-
resources.append((resource_name, resource_task))
|
|
160
|
-
self._project_resources[project] = resources
|
|
161
|
-
|
|
162
|
-
def _get_project_resources(self, project):
|
|
163
|
-
return self._project_resources.get(project, [])
|
|
164
|
-
|
|
165
|
-
def _load_project_recipes(self):
|
|
166
|
-
for project, recipes in self._project_recipes.items():
|
|
167
|
-
resources = [resource for _, resource in self._get_project_resources(project)]
|
|
168
|
-
for joltdir, src in recipes:
|
|
169
|
-
joltdir = fs.path.join(self.joltdir, joltdir) if joltdir else self.joltdir
|
|
170
|
-
recipe = NativeRecipe(fs.path.join(self._path, src), joltdir, project)
|
|
171
|
-
recipe.load()
|
|
172
|
-
for task in recipe.tasks:
|
|
173
|
-
task._resources = resources
|
|
174
|
-
attributes.requires("_resources")(task)
|
|
175
|
-
self._tasks += recipe.tasks
|
|
212
|
+
self._build_path = None
|
|
213
|
+
self._workspace_name = None
|
|
214
|
+
|
|
215
|
+
def _get_first_recipe_path(self):
|
|
216
|
+
for recipe in self.recipes:
|
|
217
|
+
return recipe.path
|
|
218
|
+
return None
|
|
176
219
|
|
|
177
|
-
def
|
|
220
|
+
def _find_workspace_path(self, searchdir):
|
|
178
221
|
for factory in _loaders:
|
|
179
222
|
loader = factory().create(searchdir)
|
|
180
223
|
if loader.recipes:
|
|
@@ -184,31 +227,63 @@ class JoltLoader(object):
|
|
|
184
227
|
if searchdir == parentdir:
|
|
185
228
|
return os.getcwd()
|
|
186
229
|
|
|
187
|
-
return self.
|
|
230
|
+
return self._find_workspace_path(parentdir)
|
|
188
231
|
|
|
189
232
|
def _get_searchpaths(self):
|
|
190
|
-
return [self.
|
|
233
|
+
return [self.workspace_path]
|
|
234
|
+
|
|
235
|
+
def load(self, registry=None):
|
|
236
|
+
"""
|
|
237
|
+
Load all recipes from the workspace directory.
|
|
191
238
|
|
|
192
|
-
|
|
193
|
-
if not self.joltdir:
|
|
194
|
-
self.set_joltdir(self._find_joltdir(os.getcwd()))
|
|
239
|
+
Optionally populate the task registry with the tasks found in the recipes.
|
|
195
240
|
|
|
196
|
-
|
|
241
|
+
Returns:
|
|
242
|
+
List of Task classes found in the recipes.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
if not self.workspace_path:
|
|
246
|
+
self.set_workspace_path(self._find_workspace_path(os.getcwd()))
|
|
247
|
+
|
|
248
|
+
if not self.workspace_path:
|
|
197
249
|
return []
|
|
198
250
|
|
|
199
251
|
for searchpath in self._get_searchpaths():
|
|
200
252
|
for factory in _loaders:
|
|
201
253
|
loader = factory().create(searchpath)
|
|
202
254
|
for recipe in loader.recipes:
|
|
255
|
+
recipe.workspace_path = os.path.relpath(recipe.path, self.workspace_path)
|
|
203
256
|
recipe.load()
|
|
204
257
|
self._recipes.append(recipe)
|
|
205
258
|
self._tasks += recipe.tasks
|
|
206
259
|
|
|
207
|
-
|
|
260
|
+
# Create workspace lock on the first loaded recipe
|
|
261
|
+
if not self._lock:
|
|
262
|
+
path = self._get_first_recipe_path()
|
|
263
|
+
if path:
|
|
264
|
+
self._lock = fasteners.InterProcessLock(path)
|
|
265
|
+
|
|
266
|
+
# Add tasks to the registry if provided
|
|
267
|
+
if registry is not None:
|
|
268
|
+
for task in self._tasks:
|
|
269
|
+
registry.add_task_class(task)
|
|
208
270
|
|
|
209
271
|
return self._tasks
|
|
210
272
|
|
|
273
|
+
def load_file(self, path, joltdir=None):
|
|
274
|
+
""" Load a single recipe file. """
|
|
275
|
+
|
|
276
|
+
for factory in _loaders:
|
|
277
|
+
loader = factory().create(path)
|
|
278
|
+
for recipe in loader.recipes:
|
|
279
|
+
joltdir = fs.path.join(self.joltdir, joltdir) if joltdir else self.joltdir
|
|
280
|
+
recipe.load(joltdir=joltdir)
|
|
281
|
+
self._recipes.append(recipe)
|
|
282
|
+
self._tasks += recipe.tasks
|
|
283
|
+
|
|
211
284
|
def load_plugin(self, filepath):
|
|
285
|
+
""" Load a single plugin file. """
|
|
286
|
+
|
|
212
287
|
plugin, ext = os.path.splitext(fs.path.basename(filepath))
|
|
213
288
|
loader = SourceFileLoader("jolt.plugins." + plugin, filepath)
|
|
214
289
|
module = ModuleType(loader.name)
|
|
@@ -217,6 +292,15 @@ class JoltLoader(object):
|
|
|
217
292
|
sys.modules[loader.name] = module
|
|
218
293
|
|
|
219
294
|
def load_plugins(self):
|
|
295
|
+
"""
|
|
296
|
+
Load all configured plugins.
|
|
297
|
+
|
|
298
|
+
Plugins are loaded from the plugin path configured in the Jolt configuration file
|
|
299
|
+
or from the default plugin path in the Jolt package.
|
|
300
|
+
|
|
301
|
+
If a plugin is already loaded, it will not be loaded again. If a plugin is not found
|
|
302
|
+
in the plugin path, it will not be loaded.
|
|
303
|
+
"""
|
|
220
304
|
searchpath = config.get("jolt", "pluginpath")
|
|
221
305
|
searchpath = searchpath.split(":") if searchpath else []
|
|
222
306
|
searchpath.append(fs.path.join(fs.path.dirname(__file__), "plugins"))
|
|
@@ -243,150 +327,230 @@ class JoltLoader(object):
|
|
|
243
327
|
|
|
244
328
|
@property
|
|
245
329
|
def tasks(self):
|
|
330
|
+
""" Returns a list of all loaded tasks. """
|
|
246
331
|
return self._tasks
|
|
247
332
|
|
|
248
333
|
@property
|
|
249
334
|
def joltdir(self):
|
|
335
|
+
""" Returns the path to the workspace. """
|
|
250
336
|
return self._path
|
|
251
337
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
338
|
+
@property
|
|
339
|
+
def workspace_name(self):
|
|
340
|
+
""" Returns the name of the workspace. """
|
|
341
|
+
return self._workspace_name or os.path.basename(self.workspace_path)
|
|
256
342
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
loader = JoltLoader.get()
|
|
343
|
+
def set_workspace_name(self, name):
|
|
344
|
+
self._workspace_name = name
|
|
260
345
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
346
|
+
@property
|
|
347
|
+
def workspace_path(self):
|
|
348
|
+
""" Returns the path to the workspace. """
|
|
349
|
+
return self._path
|
|
265
350
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
351
|
+
@contextmanager
|
|
352
|
+
@utils.locked
|
|
353
|
+
def workspace_lock(self):
|
|
354
|
+
if not self._lock:
|
|
355
|
+
yield
|
|
356
|
+
with self._lock:
|
|
357
|
+
yield
|
|
270
358
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
resource.text = resource_task
|
|
359
|
+
def set_workspace_path(self, path):
|
|
360
|
+
if not self._path or len(path) < len(self._path):
|
|
361
|
+
self._path = os.path.normpath(path) if path is not None else None
|
|
275
362
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
recipe.joltdir = joltdir
|
|
363
|
+
@property
|
|
364
|
+
def build_path(self):
|
|
365
|
+
""" Returns the path to the build directory. """
|
|
366
|
+
return self._build_path or os.path.join(self.workspace_path, "build")
|
|
281
367
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
368
|
+
@property
|
|
369
|
+
def build_path_rel(self):
|
|
370
|
+
"""" Returns the path to the build directory relative to the workspace. """
|
|
371
|
+
return os.path.relpath(self.build_path, self.workspace_path)
|
|
285
372
|
|
|
286
|
-
def
|
|
287
|
-
|
|
288
|
-
|
|
373
|
+
def set_build_path(self, path):
|
|
374
|
+
self._build_path = os.path.normpath(os.path.join(self.workspace_path, path))
|
|
375
|
+
log.debug("Jolt build path: {}", self._build_path)
|
|
289
376
|
|
|
290
|
-
for recipe in manifest.recipes:
|
|
291
|
-
recipe = Recipe(recipe.path, source=recipe.source)
|
|
292
|
-
recipe.save()
|
|
293
377
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
378
|
+
def get_workspacedir():
|
|
379
|
+
workspacedir = JoltLoader.get().workspace_path
|
|
380
|
+
assert workspacedir is not None, "No workspace present"
|
|
381
|
+
return workspacedir
|
|
297
382
|
|
|
298
|
-
for resource in project.resources:
|
|
299
|
-
loader._add_project_resource(project.name, resource.name, resource.text)
|
|
300
383
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
task.acquire_ws()
|
|
384
|
+
@contextmanager
|
|
385
|
+
def workspace_lock():
|
|
386
|
+
with JoltLoader.get().workspace_lock():
|
|
387
|
+
yield
|
|
306
388
|
|
|
307
|
-
for module in project.modules:
|
|
308
|
-
loader._add_project_module(project.name, module.src)
|
|
309
|
-
sys.path.append(fs.path.join(manifest.joltdir, module.src))
|
|
310
389
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
390
|
+
def workspace_locked(func):
|
|
391
|
+
def wrapper(*args, **kwargs):
|
|
392
|
+
with workspace_lock():
|
|
393
|
+
return func(*args, **kwargs)
|
|
394
|
+
return wrapper
|
|
314
395
|
|
|
315
|
-
# Write .jolt files into workspace
|
|
316
|
-
for file in buildenv.workspace.files:
|
|
317
|
-
if file.content:
|
|
318
|
-
with open(file.path, "w") as f:
|
|
319
|
-
f.write(file.content)
|
|
320
396
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
397
|
+
def import_workspace(buildenv: common_pb.BuildEnvironment):
|
|
398
|
+
"""
|
|
399
|
+
Import workspace from a BuildEnvironment protobuf message.
|
|
324
400
|
|
|
325
|
-
|
|
326
|
-
|
|
401
|
+
This function will create files, recipes, resources and modules in the workspace
|
|
402
|
+
based on the information in the BuildEnvironment message.
|
|
327
403
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
raise_task_error_if(
|
|
331
|
-
not isinstance(task, WorkspaceResource), task,
|
|
332
|
-
"only workspace resources are allowed in manifest")
|
|
333
|
-
task.acquire_ws()
|
|
404
|
+
The workspace tree is not pulled and checked out here. This is done by the
|
|
405
|
+
worker before it starts the executor.
|
|
334
406
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
407
|
+
"""
|
|
408
|
+
loader = JoltLoader.get()
|
|
409
|
+
loader.set_workspace_path(os.getcwd())
|
|
410
|
+
loader.set_workspace_name(buildenv.workspace.name)
|
|
411
|
+
if buildenv.workspace.builddir:
|
|
412
|
+
loader.set_build_path(buildenv.workspace.builddir)
|
|
338
413
|
|
|
414
|
+
# Write .jolt files into workspace
|
|
415
|
+
for file in buildenv.workspace.files:
|
|
416
|
+
if file.content:
|
|
417
|
+
with open(file.path, "w") as f:
|
|
418
|
+
f.write(file.content)
|
|
339
419
|
|
|
340
|
-
ManifestExtensionRegistry.add(RecipeExtension())
|
|
341
420
|
|
|
421
|
+
@workspace_locked
|
|
422
|
+
def export_workspace(tasks=None) -> common_pb.Workspace:
|
|
423
|
+
"""
|
|
424
|
+
Export workspace to a Workspace protobuf message.
|
|
342
425
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
426
|
+
This function will create a Workspace protobuf message containing all the
|
|
427
|
+
recipes, resources and modules in the workspace. If tasks is provided, only
|
|
428
|
+
the projects associated with the tasks will be exported. Otherwise, all
|
|
429
|
+
projects will be exported.
|
|
347
430
|
|
|
431
|
+
If the workspace is configured to use a remote cache, the workspace will be
|
|
432
|
+
pushed to the remote cache using the fstree tool. The tree hash of the
|
|
433
|
+
workspace will be included in the returned Workspace message.
|
|
348
434
|
|
|
349
|
-
|
|
435
|
+
"""
|
|
350
436
|
loader = JoltLoader.get()
|
|
437
|
+
tools = Tools()
|
|
438
|
+
tree = None
|
|
439
|
+
|
|
440
|
+
fstree_enabled = config.getboolean("jolt", "fstree", True)
|
|
441
|
+
if fstree_enabled:
|
|
442
|
+
fstree = tools.which("fstree")
|
|
443
|
+
if not fstree:
|
|
444
|
+
host = platform.system().lower()
|
|
445
|
+
arch = platform.machine().lower()
|
|
446
|
+
fstree = os.path.join(os.path.dirname(__file__), "bin", f"fstree-{host}-{arch}")
|
|
447
|
+
if not os.path.exists(fstree):
|
|
448
|
+
fstree_enabled = False
|
|
449
|
+
log.warning("fstree executable not found, will not push workspace to remote cache")
|
|
450
|
+
|
|
451
|
+
cache_grpc_uri = config.geturi("cache", "grpc_uri")
|
|
452
|
+
if fstree_enabled:
|
|
453
|
+
if not cache_grpc_uri:
|
|
454
|
+
fstree_enabled = False
|
|
455
|
+
log.warning("No cache gRPC URI configured, will not push workspace to remote cache")
|
|
456
|
+
else:
|
|
457
|
+
raise_error_if(cache_grpc_uri.scheme not in ["tcp"], "Invalid scheme in cache gRPC URI config: {}", cache_grpc_uri.scheme)
|
|
458
|
+
raise_error_if(not cache_grpc_uri.netloc, "Invalid network address in cache gRPC URI config: {}", cache_grpc_uri.netloc)
|
|
459
|
+
|
|
460
|
+
# Push workspace to remote cache if possible
|
|
461
|
+
if fstree_enabled and fstree and cache_grpc_uri:
|
|
462
|
+
with tools.cwd(loader.workspace_path):
|
|
463
|
+
cwd = tools.getcwd()
|
|
464
|
+
cachedir = config.get_cachedir()
|
|
465
|
+
indexhash = utils.sha1(cwd)
|
|
466
|
+
indexfile = tools.expand_path(
|
|
467
|
+
"{cachedir}/indexes/{}/{}",
|
|
468
|
+
indexhash[:2], indexhash[2:],
|
|
469
|
+
cwd=fs.posixpath.abspath(cwd),
|
|
470
|
+
cachedir=cachedir)
|
|
471
|
+
|
|
472
|
+
if not os.path.exists(indexfile):
|
|
473
|
+
process = None
|
|
474
|
+
try:
|
|
475
|
+
with log.progress("Indexing workspace for the first time", count=None, unit="objects", estimates=False) as progress:
|
|
476
|
+
process = subprocess.Popen(
|
|
477
|
+
[fstree, "write-tree", "--json", "--cache", cachedir, "--ignore", ".joltignore", "--index", indexfile, "--remote", cache_grpc_uri.geturl(), "--threads", str(tools.thread_count())],
|
|
478
|
+
stdout=subprocess.DEVNULL,
|
|
479
|
+
stderr=subprocess.PIPE,
|
|
480
|
+
cwd=tools.getcwd())
|
|
481
|
+
|
|
482
|
+
for line in iter(process.stderr.readline, b''):
|
|
483
|
+
try:
|
|
484
|
+
event = json.loads(line.decode())
|
|
485
|
+
except json.JSONDecodeError as exc:
|
|
486
|
+
log.error("Failed to decode fstree event: {}", line.decode())
|
|
487
|
+
raise exc
|
|
488
|
+
|
|
489
|
+
if event.get("type") in ["cache::add"]:
|
|
490
|
+
progress.update(1)
|
|
491
|
+
|
|
492
|
+
except Exception as exc:
|
|
493
|
+
if process:
|
|
494
|
+
process.terminate()
|
|
495
|
+
raise exc
|
|
496
|
+
finally:
|
|
497
|
+
if process:
|
|
498
|
+
process.wait()
|
|
499
|
+
process.stderr.close()
|
|
500
|
+
raise_error_if(process.returncode != 0, "Failed to index workspace")
|
|
501
|
+
|
|
502
|
+
process = None
|
|
503
|
+
try:
|
|
504
|
+
with log.progress("Pushing workspace to remote cache", count=None, unit="objects", estimates=False) as progress:
|
|
505
|
+
process = subprocess.Popen(
|
|
506
|
+
[fstree, "write-tree-push", "--json", "--cache", cachedir, "--ignore", ".joltignore", "--index", indexfile, "--remote", cache_grpc_uri.geturl(), "--threads", str(tools.thread_count())],
|
|
507
|
+
stdout=subprocess.DEVNULL,
|
|
508
|
+
stderr=subprocess.PIPE,
|
|
509
|
+
cwd=tools.getcwd())
|
|
510
|
+
|
|
511
|
+
for line in iter(process.stderr.readline, b''):
|
|
512
|
+
try:
|
|
513
|
+
event = json.loads(line.decode())
|
|
514
|
+
except json.JSONDecodeError as exc:
|
|
515
|
+
log.error("Failed to decode fstree event: {}", line.decode())
|
|
516
|
+
raise exc
|
|
517
|
+
|
|
518
|
+
if event.get("type") in ["cache::push"]:
|
|
519
|
+
tree = event.get("path")
|
|
520
|
+
log.info("Workspace tree: {} ({} objects)", tree, event.get("value"))
|
|
521
|
+
|
|
522
|
+
if event.get("type") in ["cache::remote_missing_object", "cache::remote_missing_tree"]:
|
|
523
|
+
progress.refresh()
|
|
524
|
+
|
|
525
|
+
if event.get("type") in ["cache::push_object", "cache::push_tree"]:
|
|
526
|
+
progress.update(1)
|
|
527
|
+
|
|
528
|
+
except Exception as exc:
|
|
529
|
+
if process:
|
|
530
|
+
process.terminate()
|
|
531
|
+
raise exc
|
|
532
|
+
|
|
533
|
+
finally:
|
|
534
|
+
if process:
|
|
535
|
+
process.wait()
|
|
536
|
+
process.stderr.close()
|
|
537
|
+
raise_error_if(process.returncode != 0, "Failed to push workspace to remote cache")
|
|
538
|
+
|
|
351
539
|
workspace = common_pb.Workspace(
|
|
540
|
+
builddir=loader.build_path_rel,
|
|
352
541
|
cachedir=config.get_cachedir(),
|
|
353
|
-
rootdir=loader.
|
|
542
|
+
rootdir=loader.workspace_path,
|
|
543
|
+
name=loader.workspace_name,
|
|
544
|
+
tree=tree,
|
|
354
545
|
)
|
|
355
546
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
547
|
+
if not fstree_enabled:
|
|
548
|
+
for recipe in loader.recipes:
|
|
549
|
+
workspace.files.append(
|
|
550
|
+
common_pb.File(
|
|
551
|
+
path=recipe.workspace_path,
|
|
552
|
+
content=recipe.source,
|
|
553
|
+
)
|
|
361
554
|
)
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
if tasks is None:
|
|
365
|
-
projects = loader._project_recipes.keys()
|
|
366
|
-
else:
|
|
367
|
-
projects = set([task.task.joltproject for task in tasks])
|
|
368
|
-
|
|
369
|
-
for project in filter(lambda x: x is not None, projects):
|
|
370
|
-
pb_project = common_pb.Project()
|
|
371
|
-
pb_project.name = project
|
|
372
|
-
|
|
373
|
-
for name, resource_task in loader._get_project_resources(project):
|
|
374
|
-
resource = common_pb.Project.Resource()
|
|
375
|
-
resource.alias = name
|
|
376
|
-
resource.name = resource_task
|
|
377
|
-
pb_project.resources.append(resource)
|
|
378
|
-
|
|
379
|
-
for joltdir, src in loader._get_project_recipes(project):
|
|
380
|
-
recipe = common_pb.Project.Recipe()
|
|
381
|
-
recipe.path = src
|
|
382
|
-
if joltdir:
|
|
383
|
-
recipe.workdir = joltdir
|
|
384
|
-
pb_project.recipes.append(recipe)
|
|
385
|
-
|
|
386
|
-
for path in loader._get_project_modules(project):
|
|
387
|
-
syspath = common_pb.Project.SystemPath(path=path)
|
|
388
|
-
pb_project.paths.append(syspath)
|
|
389
|
-
|
|
390
|
-
workspace.projects.append(pb_project)
|
|
391
555
|
|
|
392
556
|
return workspace
|