jolt 0.9.123__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.
Files changed (196) hide show
  1. jolt/__init__.py +80 -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 +832 -362
  6. jolt/chroot.py +156 -0
  7. jolt/cli.py +281 -162
  8. jolt/common_pb2.py +63 -0
  9. jolt/common_pb2_grpc.py +4 -0
  10. jolt/config.py +98 -41
  11. jolt/error.py +19 -4
  12. jolt/filesystem.py +2 -6
  13. jolt/graph.py +705 -117
  14. jolt/hooks.py +43 -0
  15. jolt/influence.py +122 -3
  16. jolt/loader.py +369 -121
  17. jolt/log.py +225 -63
  18. jolt/manifest.py +28 -38
  19. jolt/options.py +35 -10
  20. jolt/pkgs/abseil.py +42 -0
  21. jolt/pkgs/asio.py +25 -0
  22. jolt/pkgs/autoconf.py +41 -0
  23. jolt/pkgs/automake.py +41 -0
  24. jolt/pkgs/b2.py +31 -0
  25. jolt/pkgs/boost.py +111 -0
  26. jolt/pkgs/boringssl.py +32 -0
  27. jolt/pkgs/busybox.py +39 -0
  28. jolt/pkgs/bzip2.py +43 -0
  29. jolt/pkgs/cares.py +29 -0
  30. jolt/pkgs/catch2.py +36 -0
  31. jolt/pkgs/cbindgen.py +17 -0
  32. jolt/pkgs/cista.py +19 -0
  33. jolt/pkgs/clang.py +44 -0
  34. jolt/pkgs/cli11.py +24 -0
  35. jolt/pkgs/cmake.py +48 -0
  36. jolt/pkgs/cpython.py +196 -0
  37. jolt/pkgs/crun.py +29 -0
  38. jolt/pkgs/curl.py +38 -0
  39. jolt/pkgs/dbus.py +18 -0
  40. jolt/pkgs/double_conversion.py +24 -0
  41. jolt/pkgs/fastfloat.py +21 -0
  42. jolt/pkgs/ffmpeg.py +28 -0
  43. jolt/pkgs/flatbuffers.py +29 -0
  44. jolt/pkgs/fmt.py +27 -0
  45. jolt/pkgs/fstree.py +20 -0
  46. jolt/pkgs/gflags.py +18 -0
  47. jolt/pkgs/glib.py +18 -0
  48. jolt/pkgs/glog.py +25 -0
  49. jolt/pkgs/glslang.py +21 -0
  50. jolt/pkgs/golang.py +16 -11
  51. jolt/pkgs/googlebenchmark.py +18 -0
  52. jolt/pkgs/googletest.py +46 -0
  53. jolt/pkgs/gperf.py +15 -0
  54. jolt/pkgs/grpc.py +73 -0
  55. jolt/pkgs/hdf5.py +19 -0
  56. jolt/pkgs/help2man.py +14 -0
  57. jolt/pkgs/inja.py +28 -0
  58. jolt/pkgs/jsoncpp.py +31 -0
  59. jolt/pkgs/libarchive.py +43 -0
  60. jolt/pkgs/libcap.py +44 -0
  61. jolt/pkgs/libdrm.py +44 -0
  62. jolt/pkgs/libedit.py +42 -0
  63. jolt/pkgs/libevent.py +31 -0
  64. jolt/pkgs/libexpat.py +27 -0
  65. jolt/pkgs/libfastjson.py +21 -0
  66. jolt/pkgs/libffi.py +16 -0
  67. jolt/pkgs/libglvnd.py +30 -0
  68. jolt/pkgs/libogg.py +28 -0
  69. jolt/pkgs/libpciaccess.py +18 -0
  70. jolt/pkgs/libseccomp.py +21 -0
  71. jolt/pkgs/libtirpc.py +24 -0
  72. jolt/pkgs/libtool.py +42 -0
  73. jolt/pkgs/libunwind.py +35 -0
  74. jolt/pkgs/libva.py +18 -0
  75. jolt/pkgs/libvorbis.py +33 -0
  76. jolt/pkgs/libxml2.py +35 -0
  77. jolt/pkgs/libxslt.py +17 -0
  78. jolt/pkgs/libyajl.py +16 -0
  79. jolt/pkgs/llvm.py +81 -0
  80. jolt/pkgs/lua.py +54 -0
  81. jolt/pkgs/lz4.py +26 -0
  82. jolt/pkgs/m4.py +14 -0
  83. jolt/pkgs/make.py +17 -0
  84. jolt/pkgs/mesa.py +81 -0
  85. jolt/pkgs/meson.py +17 -0
  86. jolt/pkgs/mstch.py +28 -0
  87. jolt/pkgs/mysql.py +60 -0
  88. jolt/pkgs/nasm.py +49 -0
  89. jolt/pkgs/ncurses.py +30 -0
  90. jolt/pkgs/ng_log.py +25 -0
  91. jolt/pkgs/ninja.py +45 -0
  92. jolt/pkgs/nlohmann_json.py +25 -0
  93. jolt/pkgs/nodejs.py +19 -11
  94. jolt/pkgs/opencv.py +24 -0
  95. jolt/pkgs/openjdk.py +26 -0
  96. jolt/pkgs/openssl.py +103 -0
  97. jolt/pkgs/paho.py +76 -0
  98. jolt/pkgs/patchelf.py +16 -0
  99. jolt/pkgs/perl.py +42 -0
  100. jolt/pkgs/pkgconfig.py +64 -0
  101. jolt/pkgs/poco.py +39 -0
  102. jolt/pkgs/protobuf.py +77 -0
  103. jolt/pkgs/pugixml.py +27 -0
  104. jolt/pkgs/python.py +19 -0
  105. jolt/pkgs/qt.py +35 -0
  106. jolt/pkgs/rapidjson.py +26 -0
  107. jolt/pkgs/rapidyaml.py +28 -0
  108. jolt/pkgs/re2.py +30 -0
  109. jolt/pkgs/re2c.py +17 -0
  110. jolt/pkgs/readline.py +15 -0
  111. jolt/pkgs/rust.py +41 -0
  112. jolt/pkgs/sdl.py +28 -0
  113. jolt/pkgs/simdjson.py +27 -0
  114. jolt/pkgs/soci.py +46 -0
  115. jolt/pkgs/spdlog.py +29 -0
  116. jolt/pkgs/spirv_llvm.py +21 -0
  117. jolt/pkgs/spirv_tools.py +24 -0
  118. jolt/pkgs/sqlite.py +83 -0
  119. jolt/pkgs/ssl.py +12 -0
  120. jolt/pkgs/texinfo.py +15 -0
  121. jolt/pkgs/tomlplusplus.py +22 -0
  122. jolt/pkgs/wayland.py +26 -0
  123. jolt/pkgs/x11.py +58 -0
  124. jolt/pkgs/xerces_c.py +20 -0
  125. jolt/pkgs/xorg.py +360 -0
  126. jolt/pkgs/xz.py +29 -0
  127. jolt/pkgs/yamlcpp.py +30 -0
  128. jolt/pkgs/zeromq.py +47 -0
  129. jolt/pkgs/zlib.py +87 -0
  130. jolt/pkgs/zstd.py +33 -0
  131. jolt/plugins/alias.py +3 -0
  132. jolt/plugins/allure.py +5 -2
  133. jolt/plugins/autotools.py +66 -0
  134. jolt/plugins/cache.py +133 -0
  135. jolt/plugins/cmake.py +74 -6
  136. jolt/plugins/conan.py +238 -0
  137. jolt/plugins/cxx.py +698 -0
  138. jolt/plugins/cxxinfo.py +7 -0
  139. jolt/plugins/dashboard.py +1 -1
  140. jolt/plugins/docker.py +80 -23
  141. jolt/plugins/email.py +2 -2
  142. jolt/plugins/email.xslt +144 -101
  143. jolt/plugins/environ.py +11 -0
  144. jolt/plugins/fetch.py +141 -0
  145. jolt/plugins/gdb.py +39 -19
  146. jolt/plugins/gerrit.py +1 -14
  147. jolt/plugins/git.py +283 -85
  148. jolt/plugins/googletest.py +2 -1
  149. jolt/plugins/http.py +36 -38
  150. jolt/plugins/libtool.py +63 -0
  151. jolt/plugins/linux.py +990 -0
  152. jolt/plugins/logstash.py +4 -4
  153. jolt/plugins/meson.py +61 -0
  154. jolt/plugins/ninja-compdb.py +99 -30
  155. jolt/plugins/ninja.py +468 -166
  156. jolt/plugins/paths.py +11 -1
  157. jolt/plugins/pkgconfig.py +219 -0
  158. jolt/plugins/podman.py +136 -92
  159. jolt/plugins/python.py +137 -0
  160. jolt/plugins/remote_execution/__init__.py +0 -0
  161. jolt/plugins/remote_execution/administration_pb2.py +46 -0
  162. jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
  163. jolt/plugins/remote_execution/log_pb2.py +32 -0
  164. jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
  165. jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
  166. jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
  167. jolt/plugins/remote_execution/worker_pb2.py +38 -0
  168. jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
  169. jolt/plugins/report.py +12 -2
  170. jolt/plugins/rust.py +25 -0
  171. jolt/plugins/scheduler.py +710 -0
  172. jolt/plugins/selfdeploy/setup.py +8 -4
  173. jolt/plugins/selfdeploy.py +138 -88
  174. jolt/plugins/strings.py +35 -22
  175. jolt/plugins/symlinks.py +26 -11
  176. jolt/plugins/telemetry.py +5 -2
  177. jolt/plugins/timeline.py +13 -3
  178. jolt/plugins/volume.py +46 -48
  179. jolt/scheduler.py +589 -192
  180. jolt/tasks.py +625 -121
  181. jolt/templates/timeline.html.template +44 -47
  182. jolt/timer.py +22 -0
  183. jolt/tools.py +638 -282
  184. jolt/utils.py +211 -7
  185. jolt/version.py +1 -1
  186. jolt/xmldom.py +12 -2
  187. {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/METADATA +97 -38
  188. jolt-0.9.435.dist-info/RECORD +207 -0
  189. {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/WHEEL +1 -1
  190. jolt/plugins/amqp.py +0 -834
  191. jolt/plugins/debian.py +0 -338
  192. jolt/plugins/ftp.py +0 -181
  193. jolt/plugins/repo.py +0 -253
  194. jolt-0.9.123.dist-info/RECORD +0 -77
  195. {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/entry_points.txt +0 -0
  196. {jolt-0.9.123.dist-info → jolt-0.9.435.dist-info}/top_level.txt +0 -0
jolt/plugins/git.py CHANGED
@@ -1,11 +1,13 @@
1
1
  import os
2
2
  import pygit2
3
3
  import re
4
+ from threading import RLock
5
+ import urllib.parse
4
6
 
5
- from jolt.tasks import BooleanParameter, Export, Parameter, TaskRegistry, WorkspaceResource
7
+ from jolt.tasks import BooleanParameter, Export, IntParameter, Parameter, TaskRegistry, WorkspaceResource
6
8
  from jolt.influence import FileInfluence, HashInfluenceRegistry
7
9
  from jolt.tools import Tools
8
- from jolt.loader import JoltLoader
10
+ from jolt.loader import JoltLoader, workspace_locked
9
11
  from jolt import config
10
12
  from jolt import filesystem as fs
11
13
  from jolt import log
@@ -19,6 +21,13 @@ from jolt.error import raise_task_error_if
19
21
  log.verbose("[Git] Loaded")
20
22
 
21
23
 
24
+ def locked(func):
25
+ def _f(self, *args, **kwargs):
26
+ with self._lock:
27
+ return func(self, *args, **kwargs)
28
+ return _f
29
+
30
+
22
31
  class GitRepository(object):
23
32
  def __init__(self, url, path, relpath, refspecs=None):
24
33
  self.path = path
@@ -28,11 +37,12 @@ class GitRepository(object):
28
37
  self.url = url
29
38
  self.default_refspecs = [
30
39
  '+refs/heads/*:refs/remotes/origin/*',
31
- '+refs/tags/*:refs/remotes/origin/*',
40
+ '+refs/tags/*:refs/tags/*',
32
41
  ]
33
42
  self.refspecs = refspecs or []
34
43
  self._tree_hash = {}
35
44
  self._original_head = True
45
+ self._last_rev = None
36
46
  self._init_repo()
37
47
 
38
48
  def _init_repo(self):
@@ -66,18 +76,64 @@ class GitRepository(object):
66
76
  def is_indexed(self):
67
77
  return self.is_cloned() and fs.path.exists(self._git_index())
68
78
 
69
- def clone(self):
79
+ def clone(self, submodules=False):
70
80
  log.info("Cloning into {0}", self.path)
81
+
82
+ # Get reference repository path
83
+ refroot = config.get("git", "reference", None)
84
+ refpath = None
85
+ if refroot:
86
+ # Append <host>/<path> to the reference root
87
+ url = urllib.parse.urlparse(str(self.url))
88
+ refpath = fs.path.join(refroot, url.hostname, url.path.lstrip("/"))
89
+ refpath = fs.path.abspath(refpath)
90
+
91
+ # If the directory exists, initialize the repository instead of cloning
71
92
  if fs.path.exists(self.path):
72
93
  with self.tools.cwd(self.path):
73
- self.tools.run("git init && git remote add origin {} && git fetch",
74
- self.url, output_on_error=True)
94
+ self.tools.run("git init", output_on_error=True)
95
+
96
+ # Set the reference repository if available
97
+ if refpath:
98
+ # Check if the reference repository is a git repository
99
+ objpath = os.path.join(refpath, ".git", "objects")
100
+ objpath_bare = os.path.join(refpath, "objects")
101
+ if os.path.isdir(objpath):
102
+ refpath = objpath
103
+ elif os.path.isdir(objpath_bare):
104
+ refpath = objpath_bare
105
+ else:
106
+ refpath = None
107
+
108
+ if refpath:
109
+ self.tools.mkdir(".git/objects/info")
110
+ self.tools.write_file(".git/objects/info/alternates", refpath)
111
+
112
+ utils.call_and_catch(self.tools.run, "git remote remove origin", output=False)
113
+ self.tools.run("git remote add origin {}", self.url, output_on_error=True)
114
+ self._fetch_origin(submodules=submodules)
115
+ self.tools.run("git checkout -f FETCH_HEAD", output_on_error=True)
75
116
  else:
76
- self.tools.run("git clone {0} {1}", self.url, self.path, output_on_error=True)
117
+ # Get configurable extra clone options
118
+ extra_clone_options = config.get("git", "clone_options", "")
119
+
120
+ if refpath and os.path.isdir(refpath):
121
+ self.tools.run("git clone --reference-if-able {0} {1} {2} {3}", refpath, extra_clone_options, self.url, self.path, output_on_error=True)
122
+ else:
123
+ self.tools.run("git clone {0} {1} {2}", extra_clone_options, self.url, self.path, output_on_error=True)
124
+
77
125
  self._init_repo()
78
126
  raise_error_if(
79
127
  self.repository is None,
80
- "git: failed to clone repository '{0}'", self.relpath)
128
+ "Failed to clone repository '{0}'", self.relpath)
129
+
130
+ @utils.retried.on_exception(JoltCommandError, pattern="Command failed: git fetch", count=6, backoff=[2, 5, 10, 15, 20, 30])
131
+ def _fetch_origin(self, submodules=False):
132
+ # Get configurable extra clone options
133
+ extra_fetch_options = config.get("git", "fetch_options", "")
134
+
135
+ with self.tools.cwd(self.path):
136
+ self.tools.run("git fetch {0} {1} origin", "", extra_fetch_options, output_on_error=True)
81
137
 
82
138
  @utils.cached.instance
83
139
  def diff_unchecked(self):
@@ -95,7 +151,7 @@ class GitRepository(object):
95
151
 
96
152
  def diff(self):
97
153
  diff = self.diff_unchecked()
98
- dlim = config.getsize("git", "maxdiffsize", "1M")
154
+ dlim = config.getsize("git", "maxdiffsize", "1 MiB")
99
155
  raise_error_if(
100
156
  len(diff) > dlim,
101
157
  "Repository '{}' has uncommitted changes. Size of patch exceeds configured transfer limit ({} > {} bytes)."
@@ -105,8 +161,8 @@ class GitRepository(object):
105
161
  def patch(self, patch):
106
162
  if not patch:
107
163
  return
108
- with self.tools.cwd(self.path), self.tools.tmpdir("git") as t:
109
- patchfile = fs.path.join(t.path, "jolt.diff")
164
+ with self.tools.cwd(self.path), self.tools.tmpdir("git") as tmp:
165
+ patchfile = fs.path.join(tmp, "jolt.diff")
110
166
  with open(patchfile, "wb") as f:
111
167
  f.write(patch.encode())
112
168
  log.info("Applying patch to {0}", self.path)
@@ -133,17 +189,18 @@ class GitRepository(object):
133
189
  try:
134
190
  commit = self.repository.revparse_single(rev)
135
191
  except KeyError:
136
- self.fetch()
192
+ self.fetch(commit=rev)
137
193
  try:
138
194
  commit = self.repository.revparse_single(rev)
139
195
  except Exception:
140
- raise_error("invalid git reference: {}", rev)
196
+ raise_error("Invalid git reference: {}", rev)
141
197
  try:
142
198
  return str(commit.id)
143
199
  except Exception:
144
200
  return str(commit)
145
201
 
146
202
  @utils.cached.instance
203
+ @workspace_locked
147
204
  def write_tree(self):
148
205
  tools = Tools()
149
206
  with tools.cwd(self._git_folder()):
@@ -154,30 +211,30 @@ class GitRepository(object):
154
211
  output_on_error=True)
155
212
  return tree
156
213
 
157
- def tree_hash(self, sha=None, path="/"):
158
- # When sha is None, the caller want the tree hash of the repository's
214
+ def tree_hash(self, rev=None, path="/"):
215
+ # When rev is None, the caller want the tree hash of the repository's
159
216
  # current workspace state. If no checkout has been made, that would be the
160
217
  # tree that was written upon initialization of the repository as it
161
218
  # includes any uncommitted changes. If a checkout has been made since
162
219
  # the repo was initialized, make this an explicit request for the current
163
220
  # head - there can be no local changes.
164
- if sha is None:
221
+ if rev is None:
165
222
  if self.is_original_head():
166
223
  tree = self.repository.get(self.write_tree())
167
224
  else:
168
- sha = self.head()
225
+ rev = self.head()
169
226
 
170
227
  path = fs.path.normpath(path)
171
228
  full_path = fs.path.join(self.path, path) if path != "/" else self.path
172
229
 
173
230
  # Lookup tree hash value in cache
174
- value = self._tree_hash.get((full_path, sha))
231
+ value = self._tree_hash.get((full_path, rev))
175
232
  if value is not None:
176
233
  return value
177
234
 
178
- # Translate explicit sha to tree
179
- if sha is not None:
180
- commit = self.rev_parse(sha)
235
+ # Translate explicit rev to tree
236
+ if rev is not None:
237
+ commit = self.rev_parse(rev)
181
238
  obj = self.repository.get(commit)
182
239
  try:
183
240
  tree = obj.tree
@@ -189,7 +246,7 @@ class GitRepository(object):
189
246
  tree = tree[fs.as_posix(path)]
190
247
 
191
248
  # Update tree hash cache
192
- self._tree_hash[(full_path, sha)] = value = tree.id
249
+ self._tree_hash[(full_path, rev)] = value = tree.id
193
250
 
194
251
  return value
195
252
 
@@ -201,24 +258,49 @@ class GitRepository(object):
201
258
  with self.tools.cwd(self.path):
202
259
  return self.tools.run("git reset --hard", output_on_error=True)
203
260
 
204
- def fetch(self):
261
+ @utils.retried.on_exception(JoltCommandError, pattern="Command failed: git fetch", count=6, backoff=[2, 5, 10, 15, 20, 30])
262
+ def fetch(self, commit=None):
263
+ if commit and not self.is_valid_sha(commit):
264
+ commit = None
265
+
266
+ # Get configurable extra clone options
267
+ extra_fetch_options = config.get("git", "fetch_options", "")
268
+
205
269
  refspec = " ".join(self.default_refspecs + self.refspecs)
206
270
  with self.tools.cwd(self.path):
207
- log.info("Fetching {0} from {1}", refspec or 'commits', self.url)
208
- self.tools.run("git fetch {url} {refspec}",
209
- url=self.url,
210
- refspec=refspec or '',
211
- output_on_error=True)
212
-
213
- def checkout(self, rev):
214
- log.info("Checking out {0} in {1}", rev, self.path)
271
+ log.info("Fetching {0} from {1}", commit or refspec or 'commits', self.url)
272
+ self.tools.run(
273
+ "git fetch --force --prune {extra_fetch_options} {url} {what}",
274
+ extra_fetch_options=extra_fetch_options,
275
+ url=self.url,
276
+ what=commit or refspec or '',
277
+ output_on_error=True)
278
+
279
+ def checkout(self, rev, commit=None, submodules=False):
280
+ if rev == self._last_rev:
281
+ log.debug("Checkout skipped, already @ {}", rev)
282
+ return False
283
+ log.verbose("Checking out {0} in {1}", rev, self.path)
215
284
  with self.tools.cwd(self.path):
216
285
  try:
217
- return self.tools.run("git checkout -f {rev}", rev=rev, output=False)
286
+ self.tools.run("git checkout -f {rev}", rev=rev, output=False)
287
+ if submodules:
288
+ self._update_submodules()
218
289
  except Exception:
219
- self.fetch()
220
- return self.tools.run("git checkout -f {rev}", rev=rev, output_on_error=True)
290
+ self.fetch(commit=commit)
291
+ try:
292
+ self.tools.run("git checkout -f {rev}", rev=rev, output_on_error=True)
293
+ if submodules:
294
+ self._update_submodules()
295
+ except Exception:
296
+ raise_error("Commit does not exist in remote for '{}': {}", self.relpath, rev)
221
297
  self._original_head = False
298
+ self._last_rev = rev
299
+ return True
300
+
301
+ def _update_submodules(self):
302
+ with self.tools.cwd(self.path):
303
+ self.tools.run("git submodule update --init --recursive", output_on_error=True)
222
304
 
223
305
 
224
306
  _gits = {}
@@ -228,9 +310,9 @@ def new_git(url, path, relpath, refspecs=None):
228
310
  refspecs = utils.as_list(refspecs or [])
229
311
  try:
230
312
  git = _gits[path]
231
- raise_error_if(git.url != url, "multiple git repositories required at {}", relpath)
313
+ raise_error_if(git.url != url, "Multiple git repositories required at {}", relpath)
232
314
  raise_error_if(git.refspecs != refspecs,
233
- "conflicting refspecs detected for git repository at {}", relpath)
315
+ "Conflicting refspecs detected for git repository at {}", relpath)
234
316
  return git
235
317
  except Exception:
236
318
  git = _gits[path] = GitRepository(url, path, relpath, refspecs)
@@ -241,7 +323,7 @@ class GitInfluenceProvider(FileInfluence):
241
323
  name = "Git"
242
324
 
243
325
  def __init__(self, path):
244
- super(GitInfluenceProvider, self).__init__(path)
326
+ super().__init__(path)
245
327
  self.path = path.rstrip(fs.sep)
246
328
  self.name = GitInfluenceProvider.name
247
329
 
@@ -256,7 +338,7 @@ class GitInfluenceProvider(FileInfluence):
256
338
  return path
257
339
  ppath = path
258
340
  path = fs.path.dirname(path)
259
- raise_error("no git repository found at '{}'", self.path)
341
+ raise_error("No git repository found at '{}'", self.path)
260
342
 
261
343
  @utils.cached.instance
262
344
  def get_influence(self, task):
@@ -304,23 +386,101 @@ def influence(path, git_cls=GitInfluenceProvider):
304
386
  return _decorate
305
387
 
306
388
 
307
- class GitSrc(WorkspaceResource, FileInfluence):
308
- """ Clones a Git repo.
389
+ class ErrorDict(dict):
390
+ """ A dict that raises an error if the value of a key is None. """
391
+
392
+ def __init__(self, repo):
393
+ self.repo = repo
394
+
395
+ def __getitem__(self, key):
396
+ value = super().__getitem__(key)
397
+ raise_task_error_if(value is None, self.repo, "Git repository '{0}' referenced in influence collection before being cloned/checked out. Assign hash=true to the git requirement.", key)
398
+ return value
399
+
400
+
401
+ class Git(WorkspaceResource, FileInfluence):
309
402
  """
403
+ Resource that clones and monitors a Git repo.
404
+
405
+ By default, the repo is cloned into a build directory named after
406
+ the resource. The 'path' parameter can be used to specify a different
407
+ location relative to the workspace root.
408
+
409
+ The path of the cloned repo is made available to consuming tasks
410
+ through their 'git' attribute. The 'git' attribute is a dictionary
411
+ where the key is the name of the git repository and the value is
412
+ the relative path to the repository from the consuming task's
413
+ workspace.
414
+
415
+ The resource influences the hash of consuming tasks, causing tasks
416
+ to be re-executed if the cloned repo is modified.
417
+
418
+ The plugin must be loaded before it can be used. This is done by
419
+ importing the module, or by adding the following line to the
420
+ configuration file:
421
+
422
+ .. code-block:: ini
423
+
424
+ [git]
425
+
426
+ Example:
427
+
428
+ .. code-block:: python
429
+
430
+ from jolt.plugins import git
431
+
432
+ class Example(Task):
433
+ requires = ["git:url=https://github.com/user/repo.git"]
434
+
435
+ def run(self, deps, tools):
436
+ self.info("The git repo is located at: {git[repo]}")
437
+ with tools.cwd(self.git["repo"]):
438
+ tools.run("make")
439
+
440
+ """
441
+ name = "git"
310
442
 
311
- name = "git-src"
312
443
  url = Parameter(help="URL to the git repo to be cloned. Required.")
313
- sha = Parameter(required=False, help="Specific commit or tag to be checked out. Optional.")
444
+ """ URL to the git repo to be cloned. Required. """
445
+
446
+ rev = Parameter(required=False, help="Specific commit or tag to be checked out. Optional.")
447
+ """ Specific commit or tag to be checked out. Optional. """
448
+
449
+ hash = BooleanParameter(required=False, help="Let repo content influence the hash of consuming tasks.")
450
+ """ Let repo content influence the hash of consuming tasks. Default ``True``. Optional. """
451
+
314
452
  path = Parameter(required=False, help="Local path where the repository should be cloned.")
315
- defer = BooleanParameter(False, help="Defer cloning until a consumer task must be built.")
453
+ """ Alternative path where the repository should be cloned. Relative to ``joltdir``. Optional. """
454
+
455
+ submodules = BooleanParameter(default=False, help="Initialize and update git submodules after cloning.")
456
+ """ Initialize and update git submodules after cloning. Default ``False``. Optional. """
457
+
458
+ clean = BooleanParameter(default=False, help="Clean repository after it has been used")
459
+ """ Removes untracked files from the repository. """
460
+
316
461
  _revision = Export(value=lambda t: t._export_revision())
462
+ """ To worker exported value of the revision to be checked out. """
463
+
317
464
  _diff = Export(value=lambda t: t.git.diff(), encoded=True)
465
+ """ To worker exported value of the diff of the repo. """
318
466
 
319
467
  def __init__(self, *args, **kwargs):
320
- super(GitSrc, self).__init__(*args, **kwargs)
468
+ super().__init__(*args, **kwargs)
469
+ self._lock = RLock()
321
470
  self.joltdir = JoltLoader.get().joltdir
322
- self.relpath = str(self.path) or self._get_name()
323
- self.abspath = fs.path.join(self.joltdir, self.relpath)
471
+
472
+ # Set the path to the repo
473
+ if self.path.is_unset():
474
+ self.abspath = self.tools.builddir(utils.canonical(self.short_qualified_name), incremental="always", unique=False)
475
+ self.relpath = fs.path.relpath(self.abspath, self.tools.wsroot)
476
+ else:
477
+ self.abspath = fs.path.join(self.joltdir, str(self.path) or self._get_name())
478
+ self.relpath = fs.path.relpath(self.abspath, self.tools.wsroot)
479
+
480
+ self.abspath = fs.path.normpath(self.abspath)
481
+ self.relpath = fs.path.normpath(self.relpath)
482
+
483
+ # Create the git repository
324
484
  self.refspecs = kwargs.get("refspecs", [])
325
485
  self.git = new_git(self.url, self.abspath, self.relpath, self.refspecs)
326
486
 
@@ -331,72 +491,113 @@ class GitSrc(WorkspaceResource, FileInfluence):
331
491
  return name
332
492
 
333
493
  def _export_revision(self):
334
- return self.sha.value or self.git.head()
494
+ return self.rev.value or self.git.head()
335
495
 
336
496
  def _get_revision(self):
337
497
  if self._revision.is_imported:
338
498
  return self._revision.value
339
- if not self.sha.is_unset():
340
- return self.sha.get_value()
499
+ if not self.rev.is_unset():
500
+ return self.rev.get_value()
341
501
  return None
342
502
 
343
- def acquire(self, **kwargs):
503
+ def _assign_git(self, task, none=False):
504
+ if not hasattr(task, "git"):
505
+ task.git = ErrorDict(self)
506
+ if none:
507
+ # None means the git repo is not cloned or checked out
508
+ # and should not be included in the git dictionary
509
+ # of the consuming task yet. If the consuming task
510
+ # requires the git repo for its influence collection,
511
+ # the dict will raise an error. The solution is to
512
+ # assign hash=true to the git requirement which
513
+ # will cause the git repo to be cloned and checked out
514
+ # before the influence collection is performed.
515
+ task.git[self._get_name()] = None
516
+ else:
517
+ # Assign the git repo to the consuming task.
518
+ # The git repo is cloned and checked out before
519
+ # any influence collection is performed.
520
+ task.git[self._get_name()] = fs.path.relpath(self.abspath, task.joltdir)
521
+
522
+ def acquire(self, artifact, deps, tools, owner):
344
523
  self._acquire_ws()
524
+ self._assign_git(owner)
525
+ artifact.worktree = fs.path.relpath(self.abspath, owner.joltdir)
526
+
527
+ def release(self, artifact, deps, tools, owner):
528
+ if self.clean:
529
+ self.git.clean()
530
+ self.git.reset()
531
+
532
+ def prepare_ws_for(self, task):
533
+ """ Prepare the workspace for the task.
534
+
535
+ :param task: The task to prepare the workspace for.
536
+ """
537
+ if not self._must_influence():
538
+ # The content of the git repo is not required to influence the hash of the
539
+ # consumer task. The repo is therefore not cloned or checked out
540
+ # until the consumer is executed. Raise an error if the git repo
541
+ # is required for the influence collection of the consumer task.
542
+ self._assign_git(task, none=True)
543
+ return
544
+ # The content of the git repo is required to influence the hash of the consumer task.
545
+ self._assign_git(task)
345
546
 
346
- def acquire_ws(self):
347
- if self.defer is None or self.defer.is_false:
547
+ def acquire_ws(self, force=False):
548
+ """ Clone and/or checkout the git repo if required """
549
+ if force or self._must_influence() or self._revision.is_imported:
348
550
  self._acquire_ws()
349
551
 
552
+ @locked
350
553
  def _acquire_ws(self):
554
+ commit = None
351
555
  if not self.git.is_cloned():
352
- self.git.clone()
556
+ self.git.clone(submodules=bool(self.submodules))
353
557
  if not self._revision.is_imported:
354
558
  self.git.diff_unchecked()
559
+ else:
560
+ commit = self._revision.value
355
561
  rev = self._get_revision()
356
562
  if rev is not None:
357
563
  raise_task_error_if(
358
- not self._revision.is_imported and not self.sha.is_unset() and self.git.diff(), self,
359
- "explicit sha requested but git repo '{0}' has local changes", self.git.relpath)
564
+ not self._revision.is_imported and not self.rev.is_unset() and self.git.diff(), self,
565
+ "Explicit revision requested but git repo '{0}' has local changes, refusing checkout", self.git.relpath)
360
566
  # Should be safe to do this now
361
567
  rev = self.git.rev_parse(rev)
362
568
  if not self.git.is_head(rev) or self._revision.is_imported:
363
- self.git.checkout(rev)
364
- self.git.clean()
365
- self.git.patch(self._diff.value)
569
+ if self.git.checkout(rev, commit=commit, submodules=bool(self.submodules)):
570
+ self.git.clean()
571
+ self.git.patch(self._diff.value)
366
572
 
367
- def get_influence(self, task):
368
- return None
369
-
370
- def is_influenced_by(self, task, path):
371
- return fs.is_relative_to(path, self.abspath) and self.sha.is_set()
573
+ def _must_influence(self):
574
+ """ Check if the git repo must influence the hash of the consumer task."""
372
575
 
576
+ # If the hash parameter is set, honor it
577
+ if self.hash.is_set():
578
+ return self.hash
373
579
 
374
- TaskRegistry.get().add_task_class(GitSrc)
580
+ # If the revision parameter is not set, the git repo must influence the hash
581
+ if self.rev.is_unset():
582
+ return True
375
583
 
584
+ # If the revision parameter is set, no influence is needed since the
585
+ # revision is fixed and repository content will not change.
586
+ return False
376
587
 
377
- class Git(GitSrc):
378
- """ Clones a Git repo.
379
-
380
- Also influences the hash of consuming tasks, causing tasks to
381
- be re-executed if the cloned repo is modified.
382
-
383
- """
384
- name = "git"
385
- url = Parameter(help="URL to the git repo to be cloned. Required.")
386
- sha = Parameter(required=False, help="Specific commit or tag to be checked out. Optional.")
387
- path = Parameter(required=False, help="Local path where the repository should be cloned.")
388
- defer = None
389
- _revision = Export(value=lambda t: t._export_revision())
390
- _diff = Export(value=lambda t: t.git.diff(), encoded=True)
588
+ def is_influenced_by(self, task, path):
589
+ influencing = self._must_influence() or self.rev.is_set()
590
+ return influencing and fs.is_relative_to(path, self.abspath)
391
591
 
392
- def __init__(self, *args, **kwargs):
393
- super(Git, self).__init__(*args, **kwargs)
394
- self.influence.append(self)
592
+ def _influence(self):
593
+ influence = super()._influence()
594
+ return influence + [self] if self._must_influence() else influence
395
595
 
596
+ @locked
396
597
  @utils.cached.instance
397
598
  def get_influence(self, task):
398
599
  if not self.git.is_cloned():
399
- self.git.clone()
600
+ self.git.clone(submodules=bool(self.submodules))
400
601
  if not self._revision.is_imported:
401
602
  self.git.diff_unchecked()
402
603
  rev = self._get_revision()
@@ -411,8 +612,5 @@ class Git(GitSrc):
411
612
 
412
613
  return "{0}: {1}".format(self.git.relpath, th)
413
614
 
414
- def is_influenced_by(self, task, path):
415
- return path.startswith(self.abspath + fs.sep)
416
-
417
615
 
418
616
  TaskRegistry.get().add_task_class(Git)
@@ -212,7 +212,7 @@ def filter(default: str = "*", param: bool = True, attr: str = "filter"):
212
212
  Task class decorator controlling the GTEST_FILTER environment variable.
213
213
 
214
214
  The variable instructs test applications to only run test-cases that matches
215
- a wildcard filter string (default: *).
215
+ a wildcard filter string (default: ``*``).
216
216
 
217
217
  The value is taken from the parameter ``filter`` which is created
218
218
  by default. If no parameter is created, the value is instead taken from the class
@@ -473,6 +473,7 @@ def shuffle(default: bool = False, param: bool = True, attr: str = "shuffle"):
473
473
  @brief()
474
474
  @disabled()
475
475
  @fail_fast()
476
+ @filter()
476
477
  @junit_report()
477
478
  @repeat()
478
479
  @seed()