jolt 0.9.354__py3-none-any.whl → 0.9.370__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/loader.py CHANGED
@@ -1,4 +1,3 @@
1
- from collections import OrderedDict
2
1
  from contextlib import contextmanager
3
2
  import fasteners
4
3
  import json
@@ -11,20 +10,34 @@ import sys
11
10
  from types import ModuleType
12
11
 
13
12
  from jolt import inspection
14
- from jolt.tasks import attributes
15
- from jolt.tasks import Alias, Task, TaskGenerator, TaskRegistry, WorkspaceResource
16
- 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
17
15
  from jolt import common_pb2 as common_pb
18
16
  from jolt import config
19
17
  from jolt import filesystem as fs
20
18
  from jolt import log
21
19
  from jolt import utils
22
- from jolt.manifest import ManifestExtension
23
- from jolt.manifest import ManifestExtensionRegistry
24
20
  from jolt.tools import Tools
25
21
 
26
22
 
27
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
+
28
41
  def __init__(self, path, joltdir=None, project=None, source=None):
29
42
  self.path = path
30
43
  self.basepath = os.path.basename(path)
@@ -33,13 +46,15 @@ class Recipe(object):
33
46
  self.source = source
34
47
  self.tasks = []
35
48
 
36
- def load(self, joltdir=None):
49
+ def load(self):
50
+ """ Load the recipe source from the file system. """
37
51
  raise_error_if(self.source is not None, "recipe already loaded: {}", self.path)
38
52
 
39
53
  with open(self.path) as f:
40
54
  self.source = f.read()
41
55
 
42
56
  def save(self):
57
+ """ Save the recipe source to the file system. """
43
58
  raise_error_if(self.source is None, "recipe source unknown: {}", self.path)
44
59
 
45
60
  with open(self.path, "w") as f:
@@ -47,6 +62,8 @@ class Recipe(object):
47
62
 
48
63
 
49
64
  class NativeRecipe(Recipe):
65
+ """ Represents a Python recipe file (.jolt, .py). """
66
+
50
67
  @staticmethod
51
68
  def _is_abstract(cls):
52
69
  return cls.__dict__.get("abstract", False) or cls.__name__.startswith("_")
@@ -58,6 +75,13 @@ class NativeRecipe(Recipe):
58
75
  not NativeRecipe._is_abstract(cls)
59
76
 
60
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
+ """
61
85
  super(NativeRecipe, self).load()
62
86
 
63
87
  name = utils.canonical(self.path)
@@ -88,17 +112,42 @@ class NativeRecipe(Recipe):
88
112
 
89
113
 
90
114
  class Loader(object):
91
- 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. """
92
124
  pass
93
125
 
94
126
 
95
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
+ """
96
133
  def create(self):
97
134
  raise NotImplementedError()
98
135
 
99
136
 
100
137
  class NativeLoader(Loader):
138
+ """ A loader for Python recipe files (.jolt, .py). """
139
+
101
140
  def __init__(self, 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
+
102
151
  self._files = self._find_files(searchpath)
103
152
  self._recipes = self._load_files(self._files) if self._files else []
104
153
 
@@ -109,7 +158,7 @@ class NativeLoader(Loader):
109
158
 
110
159
  _, ext = fs.path.splitext(searchpath)
111
160
  raise_error_if(not fs.path.exists(searchpath), "File does not exist: {}", searchpath)
112
- raise_error_if(ext not in [".jolt", ".py"], "Invalid file extension: {}", ext)
161
+ raise_error_if(ext not in [".build", ".jolt", ".py"], "Invalid file extension: {}", ext)
113
162
 
114
163
  return [searchpath]
115
164
 
@@ -129,6 +178,7 @@ _loaders = []
129
178
 
130
179
 
131
180
  def register(factory):
181
+ """ Register a LoaderFactory with the JoltLoader. """
132
182
  raise_error_if(not issubclass(factory, LoaderFactory),
133
183
  "{} is not a LoaderFactory", factory.__name__)
134
184
  _loaders.append(factory)
@@ -136,12 +186,22 @@ def register(factory):
136
186
 
137
187
  @register
138
188
  class NativeLoaderFactory(LoaderFactory):
189
+ """ A factory for creating NativeLoader instances. """
139
190
  def create(self, searchpath):
140
191
  return NativeLoader(searchpath)
141
192
 
142
193
 
143
194
  @utils.Singleton
144
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
+
145
205
  filename = "*.jolt"
146
206
 
147
207
  def __init__(self):
@@ -149,61 +209,14 @@ class JoltLoader(object):
149
209
  self._recipes = []
150
210
  self._tasks = []
151
211
  self._path = None
152
- self._project_modules = OrderedDict()
153
- self._project_recipes = OrderedDict()
154
- self._project_resources = OrderedDict()
155
212
  self._build_path = None
156
213
  self._workspace_name = None
157
214
 
158
- def _add_project_module(self, project, src):
159
- modules = self._project_modules.get(project, [])
160
- modules.append(src)
161
- self._project_modules[project] = modules
162
-
163
- def _get_project_modules(self, project):
164
- return self._project_modules.get(project, [])
165
-
166
- def _add_project_recipe(self, project, joltdir, src):
167
- recipes = self._project_recipes.get(project, [])
168
- recipes.append((joltdir, src))
169
- self._project_recipes[project] = recipes
170
-
171
- def _get_project_recipes(self, project):
172
- return self._project_recipes.get(project, [])
173
-
174
215
  def _get_first_recipe_path(self):
175
216
  for recipe in self.recipes:
176
217
  return recipe.path
177
- for project, recipes in self._project_recipes.items():
178
- for joltdir, src in recipes:
179
- return fs.path.join(self._path, src)
180
218
  return None
181
219
 
182
- def _add_project_resource(self, project, resource_name, resource_task):
183
- class ProjectResource(Alias):
184
- name = project + "/" + resource_name
185
- requires = [resource_task]
186
-
187
- self._tasks.append(ProjectResource)
188
- resources = self._project_resources.get(project, [])
189
- resources.append((resource_name, resource_task))
190
- self._project_resources[project] = resources
191
-
192
- def _get_project_resources(self, project):
193
- return self._project_resources.get(project, [])
194
-
195
- def _load_project_recipes(self):
196
- for project, recipes in self._project_recipes.items():
197
- resources = [resource for _, resource in self._get_project_resources(project)]
198
- for joltdir, src in recipes:
199
- joltdir = fs.path.join(self.joltdir, joltdir) if joltdir else self.joltdir
200
- recipe = NativeRecipe(fs.path.join(self._path, src), joltdir, project)
201
- recipe.load()
202
- for task in recipe.tasks:
203
- task._resources = resources
204
- attributes.requires("_resources")(task)
205
- self._tasks += recipe.tasks
206
-
207
220
  def _find_workspace_path(self, searchdir):
208
221
  for factory in _loaders:
209
222
  loader = factory().create(searchdir)
@@ -219,7 +232,16 @@ class JoltLoader(object):
219
232
  def _get_searchpaths(self):
220
233
  return [self.workspace_path]
221
234
 
222
- def load(self, manifest=None):
235
+ def load(self, registry=None):
236
+ """
237
+ Load all recipes from the workspace directory.
238
+
239
+ Optionally populate the task registry with the tasks found in the recipes.
240
+
241
+ Returns:
242
+ List of Task classes found in the recipes.
243
+ """
244
+
223
245
  if not self.workspace_path:
224
246
  self.set_workspace_path(self._find_workspace_path(os.getcwd()))
225
247
 
@@ -234,17 +256,22 @@ class JoltLoader(object):
234
256
  self._recipes.append(recipe)
235
257
  self._tasks += recipe.tasks
236
258
 
237
- self._load_project_recipes()
238
-
239
259
  # Create workspace lock on the first loaded recipe
240
260
  if not self._lock:
241
261
  path = self._get_first_recipe_path()
242
262
  if path:
243
263
  self._lock = fasteners.InterProcessLock(path)
244
264
 
265
+ # Add tasks to the registry if provided
266
+ if registry is not None:
267
+ for task in self._tasks:
268
+ registry.add_task_class(task)
269
+
245
270
  return self._tasks
246
271
 
247
272
  def load_file(self, path, joltdir=None):
273
+ """ Load a single recipe file. """
274
+
248
275
  for factory in _loaders:
249
276
  loader = factory().create(path)
250
277
  for recipe in loader.recipes:
@@ -254,6 +281,8 @@ class JoltLoader(object):
254
281
  self._tasks += recipe.tasks
255
282
 
256
283
  def load_plugin(self, filepath):
284
+ """ Load a single plugin file. """
285
+
257
286
  plugin, ext = os.path.splitext(fs.path.basename(filepath))
258
287
  loader = SourceFileLoader("jolt.plugins." + plugin, filepath)
259
288
  module = ModuleType(loader.name)
@@ -262,6 +291,15 @@ class JoltLoader(object):
262
291
  sys.modules[loader.name] = module
263
292
 
264
293
  def load_plugins(self):
294
+ """
295
+ Load all configured plugins.
296
+
297
+ Plugins are loaded from the plugin path configured in the Jolt configuration file
298
+ or from the default plugin path in the Jolt package.
299
+
300
+ If a plugin is already loaded, it will not be loaded again. If a plugin is not found
301
+ in the plugin path, it will not be loaded.
302
+ """
265
303
  searchpath = config.get("jolt", "pluginpath")
266
304
  searchpath = searchpath.split(":") if searchpath else []
267
305
  searchpath.append(fs.path.join(fs.path.dirname(__file__), "plugins"))
@@ -288,14 +326,17 @@ class JoltLoader(object):
288
326
 
289
327
  @property
290
328
  def tasks(self):
329
+ """ Returns a list of all loaded tasks. """
291
330
  return self._tasks
292
331
 
293
332
  @property
294
333
  def joltdir(self):
334
+ """ Returns the path to the workspace. """
295
335
  return self._path
296
336
 
297
337
  @property
298
338
  def workspace_name(self):
339
+ """ Returns the name of the workspace. """
299
340
  return self._workspace_name or os.path.basename(self.workspace_path)
300
341
 
301
342
  def set_workspace_name(self, name):
@@ -303,6 +344,7 @@ class JoltLoader(object):
303
344
 
304
345
  @property
305
346
  def workspace_path(self):
347
+ """ Returns the path to the workspace. """
306
348
  return self._path
307
349
 
308
350
  @contextmanager
@@ -319,10 +361,12 @@ class JoltLoader(object):
319
361
 
320
362
  @property
321
363
  def build_path(self):
364
+ """ Returns the path to the build directory. """
322
365
  return self._build_path or os.path.join(self.workspace_path, "build")
323
366
 
324
367
  @property
325
368
  def build_path_rel(self):
369
+ """" Returns the path to the build directory relative to the workspace. """
326
370
  return os.path.relpath(self.build_path, self.workspace_path)
327
371
 
328
372
  def set_build_path(self, path):
@@ -330,98 +374,6 @@ class JoltLoader(object):
330
374
  log.debug("Jolt build path: {}", self._build_path)
331
375
 
332
376
 
333
- class RecipeExtension(ManifestExtension):
334
- def export_manifest(self, manifest, tasks):
335
- loader = JoltLoader.get()
336
-
337
- for recipe in loader.recipes:
338
- manifest_recipe = manifest.create_recipe()
339
- manifest_recipe.path = recipe.basepath
340
- manifest_recipe.source = recipe.source
341
-
342
- projects = set([task.task.joltproject for task in tasks])
343
- for project in filter(lambda x: x is not None, projects):
344
- manifest_project = manifest.create_project()
345
- manifest_project.name = project
346
-
347
- for name, resource_task in loader._get_project_resources(project):
348
- resource = manifest_project.create_resource()
349
- resource.name = name
350
- resource.text = resource_task
351
-
352
- for joltdir, src in loader._get_project_recipes(project):
353
- recipe = manifest_project.create_recipe()
354
- recipe.src = src
355
- if joltdir:
356
- recipe.joltdir = joltdir
357
-
358
- for path in loader._get_project_modules(project):
359
- module = manifest_project.create_module()
360
- module.path = path
361
-
362
- def import_manifest(self, manifest):
363
- loader = JoltLoader.get()
364
- loader.set_workspace_path(manifest.get_workspace_path() or os.getcwd())
365
- loader.set_workspace_name(manifest.get_workspace_name())
366
- if manifest.build:
367
- loader.set_build_path(manifest.build)
368
-
369
- for recipe in manifest.recipes:
370
- recipe = Recipe(recipe.path, source=recipe.source)
371
- recipe.save()
372
-
373
- for project in manifest.projects:
374
- for recipe in project.recipes:
375
- loader._add_project_recipe(project.name, recipe.joltdir, recipe.src)
376
-
377
- for resource in project.resources:
378
- loader._add_project_resource(project.name, resource.name, resource.text)
379
-
380
- # Acquire resource immediately
381
- task = TaskRegistry.get().get_task(resource.text, manifest=manifest)
382
- raise_task_error_if(not isinstance(task, WorkspaceResource), task,
383
- "only workspace resources are allowed in manifest")
384
- task.acquire_ws()
385
-
386
- for module in project.modules:
387
- loader._add_project_module(project.name, module.src)
388
- sys.path.append(fs.path.join(manifest.get_workspace_path(), module.src))
389
-
390
- def import_protobuf(self, buildenv):
391
- loader = JoltLoader.get()
392
- loader.set_workspace_path(os.getcwd())
393
- loader.set_workspace_name(buildenv.workspace.name)
394
- if buildenv.workspace.builddir:
395
- loader.set_build_path(buildenv.workspace.builddir)
396
-
397
- # Write .jolt files into workspace
398
- for file in buildenv.workspace.files:
399
- if file.content:
400
- with open(file.path, "w") as f:
401
- f.write(file.content)
402
-
403
- for project in buildenv.workspace.projects:
404
- for recipe in project.recipes:
405
- loader._add_project_recipe(project.name, recipe.workdir, recipe.path)
406
-
407
- for resource in project.resources:
408
- loader._add_project_resource(project.name, resource.alias, resource.name)
409
-
410
- # Acquire resource immediately
411
- task = TaskRegistry.get().get_task(resource.name, buildenv=buildenv)
412
- raise_task_error_if(
413
- not isinstance(task, WorkspaceResource), task,
414
- "only workspace resources are allowed in manifest")
415
- task.acquire_ws()
416
-
417
- for path in project.paths:
418
- loader._add_project_module(project.name, path.path)
419
- sys.path.append(fs.path.join(loader.workspace_path, path.path))
420
-
421
-
422
- ManifestExtensionRegistry.add(RecipeExtension())
423
-
424
-
425
377
  def get_workspacedir():
426
378
  workspacedir = JoltLoader.get().workspace_path
427
379
  assert workspacedir is not None, "No workspace present"
@@ -441,8 +393,45 @@ def workspace_locked(func):
441
393
  return wrapper
442
394
 
443
395
 
396
+ def import_workspace(buildenv: common_pb.BuildEnvironment):
397
+ """
398
+ Import workspace from a BuildEnvironment protobuf message.
399
+
400
+ This function will create files, recipes, resources and modules in the workspace
401
+ based on the information in the BuildEnvironment message.
402
+
403
+ The workspace tree is not pulled and checked out here. This is done by the
404
+ worker before it starts the executor.
405
+
406
+ """
407
+ loader = JoltLoader.get()
408
+ loader.set_workspace_path(os.getcwd())
409
+ loader.set_workspace_name(buildenv.workspace.name)
410
+ if buildenv.workspace.builddir:
411
+ loader.set_build_path(buildenv.workspace.builddir)
412
+
413
+ # Write .jolt files into workspace
414
+ for file in buildenv.workspace.files:
415
+ if file.content:
416
+ with open(file.path, "w") as f:
417
+ f.write(file.content)
418
+
419
+
444
420
  @workspace_locked
445
- def export_workspace(tasks=None):
421
+ def export_workspace(tasks=None) -> common_pb.Workspace:
422
+ """
423
+ Export workspace to a Workspace protobuf message.
424
+
425
+ This function will create a Workspace protobuf message containing all the
426
+ recipes, resources and modules in the workspace. If tasks is provided, only
427
+ the projects associated with the tasks will be exported. Otherwise, all
428
+ projects will be exported.
429
+
430
+ If the workspace is configured to use a remote cache, the workspace will be
431
+ pushed to the remote cache using the fstree tool. The tree hash of the
432
+ workspace will be included in the returned Workspace message.
433
+
434
+ """
446
435
  loader = JoltLoader.get()
447
436
  tools = Tools()
448
437
  tree = None
@@ -561,32 +550,4 @@ def export_workspace(tasks=None):
561
550
  )
562
551
  )
563
552
 
564
- if tasks is None:
565
- projects = loader._project_recipes.keys()
566
- else:
567
- projects = set([task.task.joltproject for task in tasks])
568
-
569
- for project in filter(lambda x: x is not None, projects):
570
- pb_project = common_pb.Project()
571
- pb_project.name = project
572
-
573
- for name, resource_task in loader._get_project_resources(project):
574
- resource = common_pb.Project.Resource()
575
- resource.alias = name
576
- resource.name = resource_task
577
- pb_project.resources.append(resource)
578
-
579
- for joltdir, src in loader._get_project_recipes(project):
580
- recipe = common_pb.Project.Recipe()
581
- recipe.path = src
582
- if joltdir:
583
- recipe.workdir = joltdir
584
- pb_project.recipes.append(recipe)
585
-
586
- for path in loader._get_project_modules(project):
587
- syspath = common_pb.Project.SystemPath(path=path)
588
- pb_project.paths.append(syspath)
589
-
590
- workspace.projects.append(pb_project)
591
-
592
553
  return workspace
jolt/manifest.py CHANGED
@@ -241,49 +241,3 @@ class JoltManifest(ElementTree):
241
241
  for manifest_task in self.tasks:
242
242
  self._identities[manifest_task.name] = manifest_task.identity
243
243
  return self._identities
244
-
245
- @staticmethod
246
- def export(task):
247
- manifest = JoltManifest()
248
- ManifestExtensionRegistry.export_manifest(manifest, task)
249
- return manifest
250
-
251
- def process_import(self):
252
- ManifestExtensionRegistry.import_manifest(self)
253
-
254
-
255
- class ManifestExtensionRegistry(object):
256
- extensions = []
257
-
258
- @staticmethod
259
- def add(extension, priority=0):
260
- ManifestExtensionRegistry.extensions.append((extension, priority))
261
- ManifestExtensionRegistry.extensions.sort(key=lambda x: x[1])
262
-
263
- @staticmethod
264
- def export_manifest(manifest, task):
265
- for extension, _ in ManifestExtensionRegistry.extensions:
266
- extension.export_manifest(manifest, task)
267
-
268
- @staticmethod
269
- def import_manifest(manifest):
270
- if not manifest.is_valid():
271
- return
272
- for extension, _ in ManifestExtensionRegistry.extensions:
273
- extension.import_manifest(manifest)
274
-
275
- @staticmethod
276
- def import_protobuf(pb):
277
- for extension, _ in ManifestExtensionRegistry.extensions:
278
- extension.import_protobuf(pb)
279
-
280
-
281
- class ManifestExtension(object):
282
- def export_manifest(self, manifest, task):
283
- pass
284
-
285
- def import_manifest(self, manifest):
286
- pass
287
-
288
- def import_protobuf(self, pb):
289
- pass
jolt/options.py CHANGED
@@ -1,16 +1,39 @@
1
1
 
2
2
  class JoltOptions(object):
3
+ """ Jolt options that control the behavior of builds. """
4
+
5
+ debug = False
6
+ """ Enable debug mode. Break into debugger on exceptions. """
7
+
8
+ download = True
9
+ """ Enable downloading of remote artifacts, both session and persistent. """
10
+
11
+ download_session = True
12
+ """ Enable downloading of remote session artifacts. """
13
+
14
+ keep_going = False
15
+ """ Keep going with the build after a task fails. """
16
+
17
+ local = False
18
+ """ Disable network access. """
19
+
20
+ network = False
21
+ """ Distribute tasks to workers. """
22
+
23
+ upload = True
24
+ """ Enable uploading of artifacts. """
25
+
26
+ worker = False
27
+ """ Running as a worker. """
28
+
29
+ salt = None
30
+ """ Salt for hashing (--salt). """
31
+
32
+ jobs = 1
33
+ """ Number of concurrent local tasks to run (1). """
34
+
35
+ mute = False
36
+ """ Mute task output, until a task fails. """
37
+
3
38
  def __init__(self, **kwargs):
4
- self.debug = False
5
- self.default = []
6
- self.download = True
7
- self.download_session = True
8
- self.keep_going = False
9
- self.local = False
10
- self.network = False
11
- self.upload = True
12
- self.worker = False
13
- self.salt = None
14
- self.jobs = 1
15
- self.mute = False
16
39
  self.__dict__.update(kwargs)