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.
- jolt/__init__.py +88 -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 +839 -367
- jolt/chroot.py +156 -0
- jolt/cli.py +362 -143
- jolt/common_pb2.py +63 -0
- jolt/common_pb2_grpc.py +4 -0
- jolt/config.py +99 -42
- jolt/error.py +19 -4
- jolt/expires.py +2 -2
- jolt/filesystem.py +8 -6
- jolt/graph.py +705 -117
- jolt/hooks.py +63 -1
- jolt/influence.py +129 -6
- jolt/loader.py +369 -121
- jolt/log.py +225 -63
- jolt/manifest.py +28 -38
- jolt/options.py +35 -10
- jolt/pkgs/abseil.py +42 -0
- jolt/pkgs/asio.py +25 -0
- jolt/pkgs/autoconf.py +41 -0
- jolt/pkgs/automake.py +41 -0
- jolt/pkgs/b2.py +31 -0
- jolt/pkgs/boost.py +111 -0
- jolt/pkgs/boringssl.py +32 -0
- jolt/pkgs/busybox.py +39 -0
- jolt/pkgs/bzip2.py +43 -0
- jolt/pkgs/cares.py +29 -0
- jolt/pkgs/catch2.py +36 -0
- jolt/pkgs/cbindgen.py +17 -0
- jolt/pkgs/cista.py +19 -0
- jolt/pkgs/clang.py +44 -0
- jolt/pkgs/cli11.py +23 -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 +69 -0
- jolt/pkgs/zstd.py +33 -0
- jolt/plugins/alias.py +3 -0
- jolt/plugins/allure.py +5 -2
- jolt/plugins/autotools.py +66 -0
- jolt/plugins/cache.py +133 -0
- jolt/plugins/cmake.py +74 -6
- jolt/plugins/conan.py +238 -0
- jolt/plugins/cxx.py +698 -0
- jolt/plugins/cxxinfo.py +7 -0
- jolt/plugins/dashboard.py +1 -1
- jolt/plugins/docker.py +91 -23
- jolt/plugins/email.py +5 -2
- jolt/plugins/email.xslt +144 -101
- jolt/plugins/environ.py +11 -0
- jolt/plugins/fetch.py +141 -0
- jolt/plugins/gdb.py +44 -21
- jolt/plugins/gerrit.py +1 -14
- jolt/plugins/git.py +316 -101
- jolt/plugins/googletest.py +522 -1
- jolt/plugins/http.py +36 -38
- jolt/plugins/libtool.py +63 -0
- jolt/plugins/linux.py +990 -0
- jolt/plugins/logstash.py +4 -4
- jolt/plugins/meson.py +61 -0
- jolt/plugins/ninja-compdb.py +107 -31
- jolt/plugins/ninja.py +929 -134
- jolt/plugins/paths.py +11 -1
- jolt/plugins/pkgconfig.py +219 -0
- jolt/plugins/podman.py +148 -91
- jolt/plugins/python.py +137 -0
- jolt/plugins/remote_execution/__init__.py +0 -0
- jolt/plugins/remote_execution/administration_pb2.py +46 -0
- jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
- jolt/plugins/remote_execution/log_pb2.py +32 -0
- jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
- jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
- jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
- jolt/plugins/remote_execution/worker_pb2.py +38 -0
- jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
- jolt/plugins/report.py +12 -2
- jolt/plugins/rust.py +25 -0
- jolt/plugins/scheduler.py +710 -0
- jolt/plugins/selfdeploy/setup.py +9 -4
- jolt/plugins/selfdeploy.py +138 -88
- jolt/plugins/strings.py +35 -22
- jolt/plugins/symlinks.py +26 -11
- jolt/plugins/telemetry.py +5 -2
- jolt/plugins/timeline.py +13 -3
- jolt/plugins/volume.py +46 -48
- jolt/scheduler.py +591 -191
- jolt/tasks.py +1783 -245
- jolt/templates/export.sh.template +12 -6
- jolt/templates/timeline.html.template +44 -47
- jolt/timer.py +22 -0
- jolt/tools.py +749 -302
- jolt/utils.py +245 -18
- jolt/version.py +1 -1
- jolt/version_utils.py +2 -2
- jolt/xmldom.py +12 -2
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/METADATA +98 -38
- jolt-0.9.429.dist-info/RECORD +207 -0
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/WHEEL +1 -1
- jolt/plugins/amqp.py +0 -834
- jolt/plugins/debian.py +0 -338
- jolt/plugins/ftp.py +0 -181
- jolt/plugins/ninja-cache.py +0 -64
- jolt/plugins/ninjacli.py +0 -271
- jolt/plugins/repo.py +0 -253
- jolt-0.9.76.dist-info/RECORD +0 -79
- {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/entry_points.txt +0 -0
- {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
|
|
9
|
-
from jolt.
|
|
10
|
-
from jolt
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
132
|
-
self.
|
|
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
|
|
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
|
|
228
|
+
return os.getcwd()
|
|
185
229
|
|
|
186
|
-
return self.
|
|
230
|
+
return self._find_workspace_path(parentdir)
|
|
187
231
|
|
|
188
232
|
def _get_searchpaths(self):
|
|
189
|
-
return [self.
|
|
233
|
+
return [self.workspace_path]
|
|
234
|
+
|
|
235
|
+
def load(self, registry=None):
|
|
236
|
+
"""
|
|
237
|
+
Load all recipes from the workspace directory.
|
|
190
238
|
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
recipe.save()
|
|
343
|
+
def set_workspace_name(self, name):
|
|
344
|
+
self._workspace_name = name
|
|
283
345
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
346
|
+
@property
|
|
347
|
+
def workspace_path(self):
|
|
348
|
+
""" Returns the path to the workspace. """
|
|
349
|
+
return self._path
|
|
287
350
|
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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().
|
|
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
|