jolt 0.9.76__py3-none-any.whl → 0.9.429__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.
Files changed (201) hide show
  1. jolt/__init__.py +88 -7
  2. jolt/__main__.py +9 -1
  3. jolt/bin/fstree-darwin-x86_64 +0 -0
  4. jolt/bin/fstree-linux-x86_64 +0 -0
  5. jolt/cache.py +839 -367
  6. jolt/chroot.py +156 -0
  7. jolt/cli.py +362 -143
  8. jolt/common_pb2.py +63 -0
  9. jolt/common_pb2_grpc.py +4 -0
  10. jolt/config.py +99 -42
  11. jolt/error.py +19 -4
  12. jolt/expires.py +2 -2
  13. jolt/filesystem.py +8 -6
  14. jolt/graph.py +705 -117
  15. jolt/hooks.py +63 -1
  16. jolt/influence.py +129 -6
  17. jolt/loader.py +369 -121
  18. jolt/log.py +225 -63
  19. jolt/manifest.py +28 -38
  20. jolt/options.py +35 -10
  21. jolt/pkgs/abseil.py +42 -0
  22. jolt/pkgs/asio.py +25 -0
  23. jolt/pkgs/autoconf.py +41 -0
  24. jolt/pkgs/automake.py +41 -0
  25. jolt/pkgs/b2.py +31 -0
  26. jolt/pkgs/boost.py +111 -0
  27. jolt/pkgs/boringssl.py +32 -0
  28. jolt/pkgs/busybox.py +39 -0
  29. jolt/pkgs/bzip2.py +43 -0
  30. jolt/pkgs/cares.py +29 -0
  31. jolt/pkgs/catch2.py +36 -0
  32. jolt/pkgs/cbindgen.py +17 -0
  33. jolt/pkgs/cista.py +19 -0
  34. jolt/pkgs/clang.py +44 -0
  35. jolt/pkgs/cli11.py +23 -0
  36. jolt/pkgs/cmake.py +48 -0
  37. jolt/pkgs/cpython.py +196 -0
  38. jolt/pkgs/crun.py +29 -0
  39. jolt/pkgs/curl.py +38 -0
  40. jolt/pkgs/dbus.py +18 -0
  41. jolt/pkgs/double_conversion.py +24 -0
  42. jolt/pkgs/fastfloat.py +21 -0
  43. jolt/pkgs/ffmpeg.py +28 -0
  44. jolt/pkgs/flatbuffers.py +29 -0
  45. jolt/pkgs/fmt.py +27 -0
  46. jolt/pkgs/fstree.py +20 -0
  47. jolt/pkgs/gflags.py +18 -0
  48. jolt/pkgs/glib.py +18 -0
  49. jolt/pkgs/glog.py +25 -0
  50. jolt/pkgs/glslang.py +21 -0
  51. jolt/pkgs/golang.py +16 -11
  52. jolt/pkgs/googlebenchmark.py +18 -0
  53. jolt/pkgs/googletest.py +46 -0
  54. jolt/pkgs/gperf.py +15 -0
  55. jolt/pkgs/grpc.py +73 -0
  56. jolt/pkgs/hdf5.py +19 -0
  57. jolt/pkgs/help2man.py +14 -0
  58. jolt/pkgs/inja.py +28 -0
  59. jolt/pkgs/jsoncpp.py +31 -0
  60. jolt/pkgs/libarchive.py +43 -0
  61. jolt/pkgs/libcap.py +44 -0
  62. jolt/pkgs/libdrm.py +44 -0
  63. jolt/pkgs/libedit.py +42 -0
  64. jolt/pkgs/libevent.py +31 -0
  65. jolt/pkgs/libexpat.py +27 -0
  66. jolt/pkgs/libfastjson.py +21 -0
  67. jolt/pkgs/libffi.py +16 -0
  68. jolt/pkgs/libglvnd.py +30 -0
  69. jolt/pkgs/libogg.py +28 -0
  70. jolt/pkgs/libpciaccess.py +18 -0
  71. jolt/pkgs/libseccomp.py +21 -0
  72. jolt/pkgs/libtirpc.py +24 -0
  73. jolt/pkgs/libtool.py +42 -0
  74. jolt/pkgs/libunwind.py +35 -0
  75. jolt/pkgs/libva.py +18 -0
  76. jolt/pkgs/libvorbis.py +33 -0
  77. jolt/pkgs/libxml2.py +35 -0
  78. jolt/pkgs/libxslt.py +17 -0
  79. jolt/pkgs/libyajl.py +16 -0
  80. jolt/pkgs/llvm.py +81 -0
  81. jolt/pkgs/lua.py +54 -0
  82. jolt/pkgs/lz4.py +26 -0
  83. jolt/pkgs/m4.py +14 -0
  84. jolt/pkgs/make.py +17 -0
  85. jolt/pkgs/mesa.py +81 -0
  86. jolt/pkgs/meson.py +17 -0
  87. jolt/pkgs/mstch.py +28 -0
  88. jolt/pkgs/mysql.py +60 -0
  89. jolt/pkgs/nasm.py +49 -0
  90. jolt/pkgs/ncurses.py +30 -0
  91. jolt/pkgs/ng_log.py +25 -0
  92. jolt/pkgs/ninja.py +45 -0
  93. jolt/pkgs/nlohmann_json.py +25 -0
  94. jolt/pkgs/nodejs.py +19 -11
  95. jolt/pkgs/opencv.py +24 -0
  96. jolt/pkgs/openjdk.py +26 -0
  97. jolt/pkgs/openssl.py +103 -0
  98. jolt/pkgs/paho.py +76 -0
  99. jolt/pkgs/patchelf.py +16 -0
  100. jolt/pkgs/perl.py +42 -0
  101. jolt/pkgs/pkgconfig.py +64 -0
  102. jolt/pkgs/poco.py +39 -0
  103. jolt/pkgs/protobuf.py +77 -0
  104. jolt/pkgs/pugixml.py +27 -0
  105. jolt/pkgs/python.py +19 -0
  106. jolt/pkgs/qt.py +35 -0
  107. jolt/pkgs/rapidjson.py +26 -0
  108. jolt/pkgs/rapidyaml.py +28 -0
  109. jolt/pkgs/re2.py +30 -0
  110. jolt/pkgs/re2c.py +17 -0
  111. jolt/pkgs/readline.py +15 -0
  112. jolt/pkgs/rust.py +41 -0
  113. jolt/pkgs/sdl.py +28 -0
  114. jolt/pkgs/simdjson.py +27 -0
  115. jolt/pkgs/soci.py +46 -0
  116. jolt/pkgs/spdlog.py +29 -0
  117. jolt/pkgs/spirv_llvm.py +21 -0
  118. jolt/pkgs/spirv_tools.py +24 -0
  119. jolt/pkgs/sqlite.py +83 -0
  120. jolt/pkgs/ssl.py +12 -0
  121. jolt/pkgs/texinfo.py +15 -0
  122. jolt/pkgs/tomlplusplus.py +22 -0
  123. jolt/pkgs/wayland.py +26 -0
  124. jolt/pkgs/x11.py +58 -0
  125. jolt/pkgs/xerces_c.py +20 -0
  126. jolt/pkgs/xorg.py +360 -0
  127. jolt/pkgs/xz.py +29 -0
  128. jolt/pkgs/yamlcpp.py +30 -0
  129. jolt/pkgs/zeromq.py +47 -0
  130. jolt/pkgs/zlib.py +69 -0
  131. jolt/pkgs/zstd.py +33 -0
  132. jolt/plugins/alias.py +3 -0
  133. jolt/plugins/allure.py +5 -2
  134. jolt/plugins/autotools.py +66 -0
  135. jolt/plugins/cache.py +133 -0
  136. jolt/plugins/cmake.py +74 -6
  137. jolt/plugins/conan.py +238 -0
  138. jolt/plugins/cxx.py +698 -0
  139. jolt/plugins/cxxinfo.py +7 -0
  140. jolt/plugins/dashboard.py +1 -1
  141. jolt/plugins/docker.py +91 -23
  142. jolt/plugins/email.py +5 -2
  143. jolt/plugins/email.xslt +144 -101
  144. jolt/plugins/environ.py +11 -0
  145. jolt/plugins/fetch.py +141 -0
  146. jolt/plugins/gdb.py +44 -21
  147. jolt/plugins/gerrit.py +1 -14
  148. jolt/plugins/git.py +316 -101
  149. jolt/plugins/googletest.py +522 -1
  150. jolt/plugins/http.py +36 -38
  151. jolt/plugins/libtool.py +63 -0
  152. jolt/plugins/linux.py +990 -0
  153. jolt/plugins/logstash.py +4 -4
  154. jolt/plugins/meson.py +61 -0
  155. jolt/plugins/ninja-compdb.py +107 -31
  156. jolt/plugins/ninja.py +929 -134
  157. jolt/plugins/paths.py +11 -1
  158. jolt/plugins/pkgconfig.py +219 -0
  159. jolt/plugins/podman.py +148 -91
  160. jolt/plugins/python.py +137 -0
  161. jolt/plugins/remote_execution/__init__.py +0 -0
  162. jolt/plugins/remote_execution/administration_pb2.py +46 -0
  163. jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
  164. jolt/plugins/remote_execution/log_pb2.py +32 -0
  165. jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
  166. jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
  167. jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
  168. jolt/plugins/remote_execution/worker_pb2.py +38 -0
  169. jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
  170. jolt/plugins/report.py +12 -2
  171. jolt/plugins/rust.py +25 -0
  172. jolt/plugins/scheduler.py +710 -0
  173. jolt/plugins/selfdeploy/setup.py +9 -4
  174. jolt/plugins/selfdeploy.py +138 -88
  175. jolt/plugins/strings.py +35 -22
  176. jolt/plugins/symlinks.py +26 -11
  177. jolt/plugins/telemetry.py +5 -2
  178. jolt/plugins/timeline.py +13 -3
  179. jolt/plugins/volume.py +46 -48
  180. jolt/scheduler.py +591 -191
  181. jolt/tasks.py +1783 -245
  182. jolt/templates/export.sh.template +12 -6
  183. jolt/templates/timeline.html.template +44 -47
  184. jolt/timer.py +22 -0
  185. jolt/tools.py +749 -302
  186. jolt/utils.py +245 -18
  187. jolt/version.py +1 -1
  188. jolt/version_utils.py +2 -2
  189. jolt/xmldom.py +12 -2
  190. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/METADATA +98 -38
  191. jolt-0.9.429.dist-info/RECORD +207 -0
  192. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/WHEEL +1 -1
  193. jolt/plugins/amqp.py +0 -834
  194. jolt/plugins/debian.py +0 -338
  195. jolt/plugins/ftp.py +0 -181
  196. jolt/plugins/ninja-cache.py +0 -64
  197. jolt/plugins/ninjacli.py +0 -271
  198. jolt/plugins/repo.py +0 -253
  199. jolt-0.9.76.dist-info/RECORD +0 -79
  200. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/entry_points.txt +0 -0
  201. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/top_level.txt +0 -0
jolt/loader.py CHANGED
@@ -1,22 +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 attributes
9
- from jolt.tasks import Alias, Task, TaskGenerator, TaskRegistry, WorkspaceResource
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
15
+ from jolt import common_pb2 as common_pb
11
16
  from jolt import config
12
17
  from jolt import filesystem as fs
13
18
  from jolt import log
14
19
  from jolt import utils
15
- from jolt.manifest import ManifestExtension
16
- from jolt.manifest import ManifestExtensionRegistry
20
+ from jolt.tools import Tools
17
21
 
18
22
 
19
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
+
20
41
  def __init__(self, path, joltdir=None, project=None, source=None):
21
42
  self.path = path
22
43
  self.basepath = os.path.basename(path)
@@ -26,12 +47,14 @@ class Recipe(object):
26
47
  self.tasks = []
27
48
 
28
49
  def load(self):
50
+ """ Load the recipe source from the file system. """
29
51
  raise_error_if(self.source is not None, "recipe already loaded: {}", self.path)
30
52
 
31
53
  with open(self.path) as f:
32
54
  self.source = f.read()
33
55
 
34
56
  def save(self):
57
+ """ Save the recipe source to the file system. """
35
58
  raise_error_if(self.source is None, "recipe source unknown: {}", self.path)
36
59
 
37
60
  with open(self.path, "w") as f:
@@ -39,6 +62,8 @@ class Recipe(object):
39
62
 
40
63
 
41
64
  class NativeRecipe(Recipe):
65
+ """ Represents a Python recipe file (.jolt, .py). """
66
+
42
67
  @staticmethod
43
68
  def _is_abstract(cls):
44
69
  return cls.__dict__.get("abstract", False) or cls.__name__.startswith("_")
@@ -49,7 +74,14 @@ class NativeRecipe(Recipe):
49
74
  issubclass(cls, Task) and \
50
75
  not NativeRecipe._is_abstract(cls)
51
76
 
52
- 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
+ """
53
85
  super(NativeRecipe, self).load()
54
86
 
55
87
  name = utils.canonical(self.path)
@@ -63,7 +95,7 @@ class NativeRecipe(Recipe):
63
95
  generators = []
64
96
 
65
97
  for cls in classes[TaskGenerator]:
66
- cls.joltdir = self.joltdir or os.path.dirname(self.path)
98
+ cls.joltdir = os.path.normpath(joltdir or self.joltdir or os.path.dirname(self.path))
67
99
  generators.append(cls())
68
100
 
69
101
  for generator in generators:
@@ -72,7 +104,7 @@ class NativeRecipe(Recipe):
72
104
 
73
105
  for task in classes[Task]:
74
106
  task.name = task.name or task.__name__.lower()
75
- task.joltdir = self.joltdir or os.path.dirname(self.path)
107
+ task.joltdir = os.path.normpath(joltdir or self.joltdir or os.path.dirname(self.path))
76
108
  task.joltproject = self.project
77
109
  self.tasks.append(task)
78
110
 
@@ -80,25 +112,62 @@ class NativeRecipe(Recipe):
80
112
 
81
113
 
82
114
  class Loader(object):
83
- def recipes(self):
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. """
84
124
  pass
85
125
 
86
126
 
87
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
+ """
88
133
  def create(self):
89
134
  raise NotImplementedError()
90
135
 
91
136
 
92
137
  class NativeLoader(Loader):
138
+ """ A loader for Python recipe files (.jolt, .py). """
139
+
93
140
  def __init__(self, searchpath):
94
- self._recipes = []
95
- self._find_files(searchpath)
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 []
96
153
 
97
154
  def _find_files(self, searchpath):
98
- files = glob.glob(fs.path.join(searchpath, "*.jolt"))
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 = []
99
167
  for filepath in files:
100
168
  recipe = NativeRecipe(filepath)
101
- self._recipes.append(recipe)
169
+ recipes.append(recipe)
170
+ return recipes
102
171
 
103
172
  @property
104
173
  def recipes(self):
@@ -109,6 +178,7 @@ _loaders = []
109
178
 
110
179
 
111
180
  def register(factory):
181
+ """ Register a LoaderFactory with the JoltLoader. """
112
182
  raise_error_if(not issubclass(factory, LoaderFactory),
113
183
  "{} is not a LoaderFactory", factory.__name__)
114
184
  _loaders.append(factory)
@@ -116,64 +186,38 @@ def register(factory):
116
186
 
117
187
  @register
118
188
  class NativeLoaderFactory(LoaderFactory):
189
+ """ A factory for creating NativeLoader instances. """
119
190
  def create(self, searchpath):
120
191
  return NativeLoader(searchpath)
121
192
 
122
193
 
123
194
  @utils.Singleton
124
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
+
125
205
  filename = "*.jolt"
126
206
 
127
207
  def __init__(self):
208
+ self._lock = None
128
209
  self._recipes = []
129
210
  self._tasks = []
130
211
  self._path = None
131
- self._project_modules = {}
132
- self._project_recipes = {}
133
- self._project_resources = {}
134
-
135
- def _add_project_module(self, project, src):
136
- modules = self._project_modules.get(project, [])
137
- modules.append(src)
138
- self._project_modules[project] = modules
139
-
140
- def _get_project_modules(self, project):
141
- return self._project_modules.get(project, [])
142
-
143
- def _add_project_recipe(self, project, joltdir, src):
144
- recipes = self._project_recipes.get(project, [])
145
- recipes.append((joltdir, src))
146
- self._project_recipes[project] = recipes
147
-
148
- def _get_project_recipes(self, project):
149
- return self._project_recipes.get(project, [])
150
-
151
- def _add_project_resource(self, project, resource_name, resource_task):
152
- class ProjectResource(Alias):
153
- name = project + "/" + resource_name
154
- requires = [resource_task]
155
-
156
- self._tasks.append(ProjectResource)
157
- resources = self._project_resources.get(project, [])
158
- resources.append((resource_name, resource_task))
159
- self._project_resources[project] = resources
160
-
161
- def _get_project_resources(self, project):
162
- return self._project_resources.get(project, [])
163
-
164
- def _load_project_recipes(self):
165
- for project, recipes in self._project_recipes.items():
166
- resources = [resource for _, resource in self._get_project_resources(project)]
167
- for joltdir, src in recipes:
168
- joltdir = fs.path.join(self.joltdir, joltdir) if joltdir else self.joltdir
169
- recipe = NativeRecipe(fs.path.join(self._path, src), joltdir, project)
170
- recipe.load()
171
- for task in recipe.tasks:
172
- task._resources = resources
173
- attributes.requires("_resources")(task)
174
- self._tasks += recipe.tasks
212
+ self._build_path = None
213
+ self._workspace_name = None
175
214
 
176
- def _find_joltdir(self, searchdir):
215
+ def _get_first_recipe_path(self):
216
+ for recipe in self.recipes:
217
+ return recipe.path
218
+ return None
219
+
220
+ def _find_workspace_path(self, searchdir):
177
221
  for factory in _loaders:
178
222
  loader = factory().create(searchdir)
179
223
  if loader.recipes:
@@ -181,32 +225,65 @@ class JoltLoader(object):
181
225
 
182
226
  parentdir = os.path.dirname(searchdir)
183
227
  if searchdir == parentdir:
184
- return None
228
+ return os.getcwd()
185
229
 
186
- return self._find_joltdir(parentdir)
230
+ return self._find_workspace_path(parentdir)
187
231
 
188
232
  def _get_searchpaths(self):
189
- return [self.joltdir]
233
+ return [self.workspace_path]
234
+
235
+ def load(self, registry=None):
236
+ """
237
+ Load all recipes from the workspace directory.
190
238
 
191
- def load(self, manifest=None):
192
- if not self.joltdir:
193
- self.set_joltdir(self._find_joltdir(os.getcwd()))
239
+ Optionally populate the task registry with the tasks found in the recipes.
194
240
 
195
- if not self.joltdir:
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:
196
249
  return []
197
250
 
198
251
  for searchpath in self._get_searchpaths():
199
252
  for factory in _loaders:
200
253
  loader = factory().create(searchpath)
201
254
  for recipe in loader.recipes:
255
+ recipe.workspace_path = os.path.relpath(recipe.path, self.workspace_path)
202
256
  recipe.load()
203
257
  self._recipes.append(recipe)
204
258
  self._tasks += recipe.tasks
205
259
 
206
- self._load_project_recipes()
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)
270
+
207
271
  return self._tasks
208
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
+
209
284
  def load_plugin(self, filepath):
285
+ """ Load a single plugin file. """
286
+
210
287
  plugin, ext = os.path.splitext(fs.path.basename(filepath))
211
288
  loader = SourceFileLoader("jolt.plugins." + plugin, filepath)
212
289
  module = ModuleType(loader.name)
@@ -215,6 +292,15 @@ class JoltLoader(object):
215
292
  sys.modules[loader.name] = module
216
293
 
217
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
+ """
218
304
  searchpath = config.get("jolt", "pluginpath")
219
305
  searchpath = searchpath.split(":") if searchpath else []
220
306
  searchpath.append(fs.path.join(fs.path.dirname(__file__), "plugins"))
@@ -226,6 +312,14 @@ class JoltLoader(object):
226
312
  if fs.path.exists(module):
227
313
  self.load_plugin(module)
228
314
  continue
315
+ module = fs.path.join(fs.path.dirname(__file__), path, plugin, "__init__.py")
316
+ if fs.path.exists(module):
317
+ self.load_plugin(module)
318
+ continue
319
+
320
+ @property
321
+ def projects(self):
322
+ return self._recipes
229
323
 
230
324
  @property
231
325
  def recipes(self):
@@ -233,76 +327,230 @@ class JoltLoader(object):
233
327
 
234
328
  @property
235
329
  def tasks(self):
330
+ """ Returns a list of all loaded tasks. """
236
331
  return self._tasks
237
332
 
238
333
  @property
239
334
  def joltdir(self):
335
+ """ Returns the path to the workspace. """
240
336
  return self._path
241
337
 
242
- def set_joltdir(self, value):
243
- if not self._path or len(value) < len(self._path):
244
- self._path = value
245
-
246
-
247
- class RecipeExtension(ManifestExtension):
248
- def export_manifest(self, manifest, task):
249
- loader = JoltLoader.get()
250
-
251
- for recipe in loader.recipes:
252
- manifest_recipe = manifest.create_recipe()
253
- manifest_recipe.path = recipe.basepath
254
- manifest_recipe.source = recipe.source
255
-
256
- projects = set([task.task.joltproject for task in [task] + task.extensions + task.descendants])
257
- for project in filter(lambda x: x is not None, projects):
258
- manifest_project = manifest.create_project()
259
- manifest_project.name = project
260
-
261
- for name, resource_task in loader._get_project_resources(project):
262
- resource = manifest_project.create_resource()
263
- resource.name = name
264
- resource.text = resource_task
265
-
266
- for joltdir, src in loader._get_project_recipes(project):
267
- recipe = manifest_project.create_recipe()
268
- recipe.src = src
269
- if joltdir:
270
- recipe.joltdir = joltdir
271
-
272
- for src in loader._get_project_modules(project):
273
- module = manifest_project.create_module()
274
- module.src = src
275
-
276
- def import_manifest(self, manifest):
277
- loader = JoltLoader.get()
278
- loader.set_joltdir(manifest.joltdir)
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)
279
342
 
280
- for recipe in manifest.recipes:
281
- recipe = Recipe(recipe.path, source=recipe.source)
282
- recipe.save()
343
+ def set_workspace_name(self, name):
344
+ self._workspace_name = name
283
345
 
284
- for project in manifest.projects:
285
- for recipe in project.recipes:
286
- loader._add_project_recipe(project.name, recipe.joltdir, recipe.src)
346
+ @property
347
+ def workspace_path(self):
348
+ """ Returns the path to the workspace. """
349
+ return self._path
287
350
 
288
- for resource in project.resources:
289
- loader._add_project_resource(project.name, resource.name, resource.text)
351
+ @contextmanager
352
+ @utils.locked
353
+ def workspace_lock(self):
354
+ if not self._lock:
355
+ yield
356
+ with self._lock:
357
+ yield
290
358
 
291
- # Acquire resource immediately
292
- task = TaskRegistry.get().get_task(resource.text, manifest=manifest)
293
- raise_task_error_if(not isinstance(task, WorkspaceResource), task,
294
- "only workspace resources are allowed in manifest")
295
- task.acquire_ws()
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
296
362
 
297
- for module in project.modules:
298
- loader._add_project_module(project.name, module.src)
299
- sys.path.append(fs.path.join(manifest.joltdir, module.src))
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")
300
367
 
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)
301
372
 
302
- ManifestExtensionRegistry.add(RecipeExtension())
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)
303
376
 
304
377
 
305
378
  def get_workspacedir():
306
- workspacedir = JoltLoader.get().joltdir
379
+ workspacedir = JoltLoader.get().workspace_path
307
380
  assert workspacedir is not None, "No workspace present"
308
381
  return workspacedir
382
+
383
+
384
+ @contextmanager
385
+ def workspace_lock():
386
+ with JoltLoader.get().workspace_lock():
387
+ yield
388
+
389
+
390
+ def workspace_locked(func):
391
+ def wrapper(*args, **kwargs):
392
+ with workspace_lock():
393
+ return func(*args, **kwargs)
394
+ return wrapper
395
+
396
+
397
+ def import_workspace(buildenv: common_pb.BuildEnvironment):
398
+ """
399
+ Import workspace from a BuildEnvironment protobuf message.
400
+
401
+ This function will create files, recipes, resources and modules in the workspace
402
+ based on the information in the BuildEnvironment message.
403
+
404
+ The workspace tree is not pulled and checked out here. This is done by the
405
+ worker before it starts the executor.
406
+
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)
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)
419
+
420
+
421
+ @workspace_locked
422
+ def export_workspace(tasks=None) -> common_pb.Workspace:
423
+ """
424
+ Export workspace to a Workspace protobuf message.
425
+
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.
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.
434
+
435
+ """
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
+
539
+ workspace = common_pb.Workspace(
540
+ builddir=loader.build_path_rel,
541
+ cachedir=config.get_cachedir(),
542
+ rootdir=loader.workspace_path,
543
+ name=loader.workspace_name,
544
+ tree=tree,
545
+ )
546
+
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
+ )
554
+ )
555
+
556
+ return workspace