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/cli.py CHANGED
@@ -1,11 +1,12 @@
1
1
  import atexit
2
2
  import click
3
+ import datetime
4
+ import os
5
+ import platform
3
6
  import subprocess
4
7
  import sys
5
- import datetime
6
8
  import uuid
7
9
  import webbrowser
8
- from os import _exit, environ, getcwd
9
10
 
10
11
  from jolt.tasks import Task, TaskRegistry, Parameter
11
12
  from jolt import scheduler
@@ -17,13 +18,12 @@ from jolt import log
17
18
  from jolt import __version__
18
19
  from jolt.log import logfile
19
20
  from jolt import config
20
- from jolt.loader import JoltLoader
21
+ from jolt.loader import JoltLoader, import_workspace
21
22
  from jolt import tools
22
23
  from jolt import utils
23
24
  from jolt.influence import HashInfluenceRegistry
24
25
  from jolt.options import JoltOptions
25
26
  from jolt import hooks
26
- from jolt.manifest import JoltManifest
27
27
  from jolt.error import JoltError
28
28
  from jolt.error import raise_error
29
29
  from jolt.error import raise_error_if
@@ -31,7 +31,7 @@ from jolt.error import raise_task_error_if
31
31
  from jolt.plugins import report
32
32
 
33
33
  debug_enabled = False
34
- workdir = getcwd()
34
+ workdir = os.getcwd()
35
35
 
36
36
 
37
37
  class ArgRequiredUnless(click.Argument):
@@ -55,10 +55,12 @@ class PluginGroup(click.Group):
55
55
 
56
56
  if cmd_name in ["export", "inspect"]:
57
57
  log.set_level(log.SILENCE)
58
- elif ctx.params.get("verbose", False):
59
- log.set_level(log.VERBOSE)
60
- elif ctx.params.get("extra_verbose", False):
58
+ elif ctx.params.get("verbose") >= 3:
59
+ log.set_level(log.EXCEPTION)
60
+ elif ctx.params.get("verbose") >= 2:
61
61
  log.set_level(log.DEBUG)
62
+ elif ctx.params.get("verbose") >= 1:
63
+ log.set_level(log.VERBOSE)
62
64
 
63
65
  config_files = ctx.params.get("config_file") or []
64
66
  for config_file in config_files:
@@ -73,10 +75,12 @@ class PluginGroup(click.Group):
73
75
 
74
76
  @click.group(cls=PluginGroup, invoke_without_command=True)
75
77
  @click.version_option(__version__)
76
- @click.option("-v", "--verbose", is_flag=True, help="Verbose output.")
77
- @click.option("-vv", "--extra-verbose", is_flag=True, help="Extra verbose output.")
78
+ @click.option("-v", "--verbose", count=True, help="Verbose output (repeat to raise verbosity).")
78
79
  @click.option("-c", "--config", "config_file", multiple=True, type=str,
79
80
  help="Load a configuration file or set a configuration key.")
81
+ @click.option("-C", "--chdir", type=str,
82
+ help="Change working directory before executing command.")
83
+ @click.option("--interpreter", "machine_interface", type=str, help="Used for debugging.", hidden=True)
80
84
  @click.option("-d", "--debugger", is_flag=True,
81
85
  help="Attach debugger on exception.")
82
86
  @click.option("-p", "--profile", is_flag=True, hidden=True,
@@ -87,6 +91,7 @@ class PluginGroup(click.Group):
87
91
  help="Add salt as task influence.")
88
92
  @click.option("-g", "--debug", is_flag=True, default=False, hidden=True,
89
93
  help="Start debug shell before executing task.")
94
+ @click.option("-m", "--mute", is_flag=True, help="Display task log only if it fails.")
90
95
  @click.option("-n", "--network", is_flag=True, default=False, hidden=True,
91
96
  help="Build on network.")
92
97
  @click.option("-l", "--local", is_flag=True, default=False, hidden=True,
@@ -97,8 +102,8 @@ class PluginGroup(click.Group):
97
102
  help="Number of tasks allowed to execute in parallel (1). ")
98
103
  @click.option("-h", "--help", is_flag=True, help="Show this message and exit.")
99
104
  @click.pass_context
100
- def cli(ctx, verbose, extra_verbose, config_file, debugger, profile,
101
- force, salt, debug, network, local, keep_going, jobs, help):
105
+ def cli(ctx, verbose, config_file, debugger, profile,
106
+ force, salt, debug, mute, network, local, keep_going, jobs, help, machine_interface, chdir):
102
107
  """
103
108
  A task execution tool.
104
109
 
@@ -122,11 +127,24 @@ def cli(ctx, verbose, extra_verbose, config_file, debugger, profile,
122
127
  global debug_enabled
123
128
  debug_enabled = debugger
124
129
 
130
+ if machine_interface:
131
+ log.enable_gdb()
132
+
133
+ if ctx.invoked_subcommand not in ["log", "report"]:
134
+ log.start_file_log()
135
+
136
+ if chdir:
137
+ global workdir
138
+ workdir = chdir
139
+ os.chdir(workdir)
140
+
141
+ log.verbose("Jolt version: {}", __version__)
125
142
  log.verbose("Jolt command: {}", " ".join([fs.path.basename(sys.argv[0])] + sys.argv[1:]))
126
- log.verbose("Jolt host: {}", environ.get("HOSTNAME", "localhost"))
143
+ log.verbose("Jolt host: {}", os.environ.get("HOSTNAME", "localhost"))
127
144
  log.verbose("Jolt install path: {}", fs.path.dirname(__file__))
145
+ log.verbose("Jolt workdir: {}", workdir)
128
146
 
129
- if ctx.invoked_subcommand in ["config"]:
147
+ if ctx.invoked_subcommand in ["config", "executor", "log"]:
130
148
  # Don't attempt to load any task recipes as they might require
131
149
  # plugins that are not yet configured.
132
150
  return
@@ -138,27 +156,13 @@ def cli(ctx, verbose, extra_verbose, config_file, debugger, profile,
138
156
  print(ctx.get_help())
139
157
  sys.exit(0)
140
158
 
141
- manifest = JoltManifest()
142
- utils.call_and_catch(manifest.parse)
143
- manifest.process_import()
144
- ctx.obj["manifest"] = manifest
145
-
146
- if manifest.version:
147
- from jolt.version_utils import requirement, version
148
- req = requirement(manifest.version)
149
- ver = version(__version__)
150
- raise_error_if(not req.satisfied(ver),
151
- "This project requires Jolt version {} (running {})",
152
- req, __version__)
153
-
159
+ registry = TaskRegistry.get()
154
160
  loader = JoltLoader.get()
155
- tasks = loader.load()
156
- for cls in tasks:
157
- TaskRegistry.get().add_task_class(cls)
161
+ loader.load(registry)
158
162
 
159
163
  if ctx.invoked_subcommand in ["build", "clean"] and loader.joltdir:
160
164
  ctx.obj["workspace_lock"] = utils.LockFile(
161
- fs.path.join(loader.joltdir, "build"),
165
+ loader.build_path,
162
166
  log.info, "Workspace is locked by another process, please wait...")
163
167
  atexit.register(ctx.obj["workspace_lock"].close)
164
168
 
@@ -167,8 +171,8 @@ def cli(ctx, verbose, extra_verbose, config_file, debugger, profile,
167
171
  if ctx.invoked_subcommand is None:
168
172
  task = config.get("jolt", "default", "default")
169
173
  taskname, _ = utils.parse_task_name(task)
170
- if TaskRegistry.get().get_task_class(taskname) is not None:
171
- ctx.invoke(build, task=[task], force=force, salt=salt, debug=debug,
174
+ if registry.get_task_class(taskname) is not None:
175
+ ctx.invoke(build, task=[task], force=force, salt=salt, debug=debug, mute=mute,
172
176
  network=network, local=local, keep_going=keep_going, jobs=jobs)
173
177
  else:
174
178
  print(cli.get_help(ctx))
@@ -176,11 +180,8 @@ def cli(ctx, verbose, extra_verbose, config_file, debugger, profile,
176
180
 
177
181
 
178
182
  def _autocomplete_tasks(ctx, args, incomplete):
179
- manifest = JoltManifest()
180
- utils.call_and_catch(manifest.parse)
181
- manifest.process_import()
182
-
183
- tasks = JoltLoader.get().load()
183
+ loader = JoltLoader.get()
184
+ tasks = loader.load()
184
185
  tasks = [task.name for task in tasks if task.name.startswith(incomplete or '')]
185
186
  return sorted(tasks)
186
187
 
@@ -198,10 +199,14 @@ def _autocomplete_tasks(ctx, args, incomplete):
198
199
  @click.option("-l", "--local", is_flag=True, default=False, help="Disable remote cache access.")
199
200
  @click.option("-n", "--network", is_flag=True, default=False, help="Distribute tasks to network workers.")
200
201
  @click.option("-s", "--salt", type=str, help="Add salt as hash influence for all tasks in dependency tree.", metavar="SALT")
202
+ @click.option("-m", "--mute", is_flag=True, help="Display task log only if it fails.")
203
+ @click.option("-v", "--verbose", count=True, help="Verbose output (repeat to raise verbosity).")
201
204
  @click.option("--result", type=click.Path(), hidden=True,
202
205
  help="Write result manifest to this file.")
203
206
  @click.option("--no-download", is_flag=True, default=False,
204
- help="Don't download artifacts from remote storage")
207
+ help="Don't download any artifacts from remote storage")
208
+ @click.option("--no-download-persistent", is_flag=True, default=False,
209
+ help="Don't download persistent artifacts from remote storage (only session artifacts)")
205
210
  @click.option("--no-upload", is_flag=True, default=False,
206
211
  help="Don't upload artifacts to remote storage")
207
212
  @click.option("--download", is_flag=True, default=False,
@@ -212,11 +217,13 @@ def _autocomplete_tasks(ctx, args, incomplete):
212
217
  help="Don't prune cached artifacts from the build graph. This option can be used to populate the local cache with remotely cached dependency artifacts.")
213
218
  @click.option("--worker", is_flag=True, default=False,
214
219
  help="Run with the worker build strategy", hidden=True)
220
+ @click.option("--environ", type=click.Path(), help="Import build environment from protobuf", hidden=True)
215
221
  @click.pass_context
216
222
  @hooks.cli_build
217
223
  def build(ctx, task, network, keep_going, default, local,
218
- no_download, no_upload, download, upload, worker, force,
219
- salt, copy, debug, result, jobs, no_prune):
224
+ no_download, no_download_persistent, no_upload, download, upload, worker, force,
225
+ salt, copy, debug, result, jobs, no_prune, verbose,
226
+ mute, environ):
220
227
  """
221
228
  Build task artifact.
222
229
 
@@ -248,6 +255,7 @@ def build(ctx, task, network, keep_going, default, local,
248
255
  are removed before execution starts.
249
256
 
250
257
  """
258
+
251
259
  raise_error_if(network and local,
252
260
  "The -n and -l flags are mutually exclusive")
253
261
 
@@ -260,8 +268,14 @@ def build(ctx, task, network, keep_going, default, local,
260
268
  raise_error_if(no_upload and upload,
261
269
  "The --upload and --no-upload flags are mutually exclusive")
262
270
 
263
- duration = utils.duration()
271
+ if verbose >= 3:
272
+ log.set_level(log.EXCEPTION)
273
+ elif verbose >= 2:
274
+ log.set_level(log.DEBUG)
275
+ elif verbose >= 1:
276
+ log.set_level(log.VERBOSE)
264
277
 
278
+ ts_start = utils.duration()
265
279
  task = list(task)
266
280
  task = [utils.stable_task_name(t) for t in task]
267
281
 
@@ -271,6 +285,7 @@ def build(ctx, task, network, keep_going, default, local,
271
285
  else:
272
286
  _download = config.getboolean("jolt", "download", True)
273
287
  _upload = config.getboolean("jolt", "upload", True)
288
+ _download_session = _download
274
289
 
275
290
  if local:
276
291
  _download = False
@@ -278,23 +293,60 @@ def build(ctx, task, network, keep_going, default, local,
278
293
  else:
279
294
  if no_download:
280
295
  _download = False
296
+ _download_session = False
297
+ if no_download_persistent:
298
+ _download = False
281
299
  if no_upload:
282
300
  _upload = False
283
301
  if download:
284
302
  _download = True
303
+ _download_session = True
285
304
  if upload:
286
305
  _upload = True
287
306
 
288
- options = JoltOptions(network=network,
289
- local=local,
290
- download=_download,
291
- upload=_upload,
292
- keep_going=keep_going,
293
- default=default,
294
- worker=worker,
295
- debug=debug,
296
- salt=salt,
297
- jobs=jobs)
307
+ if keep_going:
308
+ config.set_keep_going(True)
309
+
310
+ # Import build environment from protobuf if provided
311
+ buildenv = None
312
+ if environ:
313
+ with open(environ, "rb") as f:
314
+ from jolt import common_pb2 as common_pb
315
+ buildenv = common_pb.BuildEnvironment()
316
+ try:
317
+ buildenv.ParseFromString(f.read())
318
+ except Exception as e:
319
+ raise_error("Failed to parse build environment protobuf: {}", e)
320
+
321
+ # Import log level
322
+ log.set_level_pb(buildenv.loglevel)
323
+
324
+ # Import workspace
325
+ import_workspace(buildenv)
326
+
327
+ # Import configuration snippet
328
+ config.import_config(buildenv.config)
329
+
330
+ # Import configuration parameters (-c params.key)
331
+ config.import_params({param.key: param.value for param in buildenv.parameters})
332
+
333
+ # Import default parameters (-d taskname:param=value)
334
+ default = utils.as_list(default)
335
+ default += buildenv.task_default_parameters
336
+
337
+ options = JoltOptions(
338
+ network=network,
339
+ local=local,
340
+ download=_download,
341
+ download_session=_download_session,
342
+ upload=_upload,
343
+ keep_going=keep_going,
344
+ default=default,
345
+ worker=worker,
346
+ debug=debug,
347
+ salt=salt,
348
+ jobs=jobs,
349
+ mute=mute)
298
350
 
299
351
  acache = cache.ArtifactCache.get(options)
300
352
 
@@ -316,31 +368,29 @@ def build(ctx, task, network, keep_going, default, local,
316
368
  for params in default:
317
369
  registry.set_default_parameters(params)
318
370
 
319
- manifest = ctx.obj["manifest"]
371
+ log.info("Started: {}", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
320
372
 
321
- for mb in manifest.builds:
322
- for mt in mb.tasks:
323
- task.append(mt.name)
324
- for mt in mb.defaults:
325
- registry.set_default_parameters(mt.name)
373
+ gb = graph.GraphBuilder(registry, acache, options, progress=True, buildenv=buildenv)
374
+ dag = gb.build(task)
326
375
 
376
+ # If asked to force rebuild, taint all goal tasks
327
377
  if force:
328
- for goal in task:
329
- registry.get_task(goal, manifest=manifest).taint = uuid.uuid4()
378
+ for goal in dag.goals:
379
+ goal.get_extended_task().taint()
330
380
 
331
- log.info("Started: {}", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
332
-
333
- gb = graph.GraphBuilder(registry, manifest, options, progress=True)
334
- dag = gb.build(task)
381
+ # Collect information about artifact presence before starting prune or build
382
+ acache.precheck(dag.persistent_artifacts, remote=not local)
335
383
 
384
+ # Prune the graph to remove tasks that are already available locally or remotely
336
385
  if not no_prune:
337
- gp = graph.GraphPruner(strategy)
386
+ gp = graph.GraphPruner(acache, strategy)
338
387
  dag = gp.prune(dag)
339
388
 
340
389
  goal_tasks = dag.goals
341
390
  goal_task_duration = 0
342
391
 
343
- queue = scheduler.TaskQueue(strategy)
392
+ session = executors.create_session(dag) if options.network else {}
393
+ queue = scheduler.TaskQueue()
344
394
 
345
395
  try:
346
396
  if not dag.has_tasks():
@@ -354,16 +404,20 @@ def build(ctx, task, network, keep_going, default, local,
354
404
  debug=debug)
355
405
 
356
406
  with progress:
407
+ in_progress = set()
408
+
357
409
  while dag.has_tasks() or not queue.empty():
358
410
  # Find all tasks ready to be executed
359
- leafs = dag.select(lambda graph, task: task.is_ready())
411
+ leafs = dag.select(lambda graph, task: task.is_ready() and task not in in_progress)
360
412
 
361
413
  # Order the tasks by their weights to improve build times
362
414
  leafs.sort(key=lambda x: x.weight)
363
415
 
364
416
  while leafs:
365
417
  task = leafs.pop()
366
- queue.submit(acache, task)
418
+ executor = strategy.create_executor(session, task)
419
+ queue.submit(executor)
420
+ in_progress.add(task)
367
421
 
368
422
  task, error = queue.wait()
369
423
 
@@ -373,6 +427,7 @@ def build(ctx, task, network, keep_going, default, local,
373
427
  elif task.is_goal() and task.duration_running:
374
428
  goal_task_duration += task.duration_running.seconds
375
429
 
430
+ # Unpack tasks with overridden unpack() method
376
431
  if not task.is_resource():
377
432
  if no_prune and task.task.unpack.__func__ is not Task.unpack:
378
433
  with acache.get_context(task):
@@ -380,36 +435,50 @@ def build(ctx, task, network, keep_going, default, local,
380
435
 
381
436
  progress.update(1)
382
437
 
438
+ if no_prune and task.is_workspace_resource():
439
+ task.task.acquire_ws(force=True)
440
+
383
441
  if not keep_going and error is not None:
384
442
  queue.abort()
443
+ executors.shutdown()
444
+ task.raise_for_status()
385
445
  raise error
386
446
 
387
- if dag.failed:
447
+ if dag.failed or dag.unstable:
388
448
  log.error("List of failed tasks")
389
- for failed in dag.failed:
449
+ for failed in dag.failed + dag.unstable:
390
450
  log.error("- {}", failed.log_name.strip("()"))
451
+
452
+ for failed_task in dag.failed:
453
+ failed_task.raise_for_status()
454
+ if dag.failed:
391
455
  raise_error("No more tasks could be executed")
392
456
 
393
- for goal in goal_tasks:
394
- if acache.is_available_locally(goal):
395
- with acache.get_artifact(goal) as artifact:
396
- log.info("Location: {0}", artifact.path)
397
- if copy:
398
- artifact.copy("*", utils.as_dirpath(fs.path.join(workdir, click.format_filename(copy))), symlinks=True)
399
457
  except KeyboardInterrupt:
400
458
  print()
401
459
  log.warning("Interrupted by user")
402
460
  try:
403
461
  queue.abort()
462
+ executors.shutdown()
404
463
  sys.exit(1)
405
464
  except KeyboardInterrupt:
406
465
  print()
407
466
  log.warning("Interrupted again, exiting")
408
- _exit(1)
467
+ os._exit(1)
409
468
  finally:
469
+ queue.shutdown()
470
+
471
+ for task in goal_tasks:
472
+ for artifact in task.artifacts:
473
+ if acache.is_available_locally(artifact):
474
+ log.info("Location: {0}", artifact.path)
475
+ if copy:
476
+ dst = utils.as_dirpath(fs.path.join(workdir, click.format_filename(copy)))
477
+ artifact.copy("*", dst, symlinks=True)
478
+
410
479
  log.info("Ended: {}", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
411
480
  log.info("Total execution time: {0} {1}",
412
- str(duration),
481
+ str(ts_start),
413
482
  str(queue.duration_acc) if network else '')
414
483
  if result:
415
484
  with report.update() as manifest:
@@ -445,7 +514,7 @@ def clean(ctx, task, deps, expired):
445
514
  if task:
446
515
  task = [utils.stable_task_name(t) for t in task]
447
516
  registry = TaskRegistry.get()
448
- dag = graph.GraphBuilder(registry, ctx.obj["manifest"]).build(task)
517
+ dag = graph.GraphBuilder(registry, acache).build(task)
449
518
  if deps:
450
519
  tasks = dag.tasks
451
520
  else:
@@ -590,11 +659,10 @@ def display(ctx, task, reverse=None, show_cache=False, prune=False):
590
659
 
591
660
  """
592
661
  registry = TaskRegistry.get()
593
- gb = graph.GraphBuilder(registry, ctx.obj["manifest"])
594
- dag = gb.build(task, influence=show_cache)
595
-
596
662
  options = JoltOptions()
597
663
  acache = cache.ArtifactCache.get(options)
664
+ gb = graph.GraphBuilder(registry, acache)
665
+ dag = gb.build(task, influence=show_cache)
598
666
 
599
667
  if reverse:
600
668
  def iterator(task):
@@ -658,7 +726,14 @@ def docs():
658
726
  """
659
727
  Opens the Jolt documentation in the default webbrowser.
660
728
  """
661
- webbrowser.open(config.get("jolt", "docs", "http://jolt.readthedocs.io/"))
729
+ url = config.get("jolt", "docs", "http://jolt.readthedocs.io/")
730
+ success = False
731
+ try:
732
+ success = webbrowser.open(url)
733
+ except Exception:
734
+ pass
735
+ if not success:
736
+ print(f"Failed to open web browser. Visit {url} manually.")
662
737
 
663
738
 
664
739
  @cli.command()
@@ -683,15 +758,14 @@ def download(ctx, task, deps, copy, copy_all):
683
758
  if copy_all:
684
759
  deps = True
685
760
 
686
- manifest = ctx.obj["manifest"]
687
761
  options = JoltOptions()
688
762
  acache = cache.ArtifactCache.get(options)
689
763
  hooks.TaskHookRegistry.get(options)
690
764
  executors = scheduler.ExecutorRegistry.get(options)
691
765
  registry = TaskRegistry.get()
692
766
  strategy = scheduler.DownloadStrategy(executors, acache)
693
- queue = scheduler.TaskQueue(strategy)
694
- gb = graph.GraphBuilder(registry, manifest, options, progress=True)
767
+ queue = scheduler.TaskQueue()
768
+ gb = graph.GraphBuilder(registry, acache, options, progress=True)
695
769
  dag = gb.build(task)
696
770
 
697
771
  if not deps:
@@ -704,20 +778,24 @@ def download(ctx, task, deps, copy, copy_all):
704
778
 
705
779
  try:
706
780
  with log.progress("Progress", dag.number_of_tasks(), " tasks", estimates=False, debug=False) as p:
781
+ in_progress = set()
782
+
707
783
  while dag.has_tasks() or not queue.empty():
708
- leafs = dag.select(lambda graph, task: task.is_ready())
784
+ leafs = dag.select(lambda graph, task: task.is_ready() and task not in in_progress)
709
785
 
710
786
  while leafs:
711
787
  task = leafs.pop()
712
- queue.submit(acache, task)
788
+ executor = strategy.create_executor({}, task)
789
+ queue.submit(executor)
790
+ in_progress.add(task)
713
791
 
714
792
  task, error = queue.wait()
715
793
  p.update(1)
716
794
 
717
795
  copy_tasks = goal_tasks if not copy_all else all_tasks
718
796
  for goal in copy_tasks:
719
- if acache.is_available_locally(goal):
720
- with acache.get_artifact(goal) as artifact:
797
+ if goal.is_available_locally():
798
+ for artifact in goal.artifacts:
721
799
  if copy:
722
800
  log.info("Copying: {0}", artifact.path)
723
801
  artifact.copy("*", utils.as_dirpath(fs.path.join(workdir, click.format_filename(copy))), symlinks=True)
@@ -732,65 +810,19 @@ def download(ctx, task, deps, copy, copy_all):
732
810
  log.warning("Interrupted by user")
733
811
  try:
734
812
  queue.abort()
813
+ executors.shutdown()
735
814
  sys.exit(1)
736
815
  except KeyboardInterrupt:
737
816
  print()
738
817
  log.warning("Interrupted again, exiting")
739
- _exit(1)
818
+ os._exit(1)
819
+
740
820
  except Exception as e:
741
821
  log.set_interactive(True)
742
822
  raise e
743
823
 
744
-
745
- @cli.command(hidden=True)
746
- @click.argument("task", type=str, nargs=-1, required=True)
747
- @click.option("-r", "--remove", is_flag=True, help="Remove tasks from existing manifest.")
748
- @click.option("-d", "--default", type=str, multiple=True, help="Override default parameter values.")
749
- @click.option("-o", "--output", type=str, default="default.joltxmanifest", help="Manifest filename.")
750
- @click.pass_context
751
- def freeze(ctx, task, default, output, remove):
752
- """
753
- Freeze the identity of a task.
754
-
755
- <WIP>
756
- """
757
- manifest = ctx.obj["manifest"]
758
-
759
- options = JoltOptions(default=default)
760
- acache = cache.ArtifactCache.get(options)
761
- scheduler.ExecutorRegistry.get(options)
762
- registry = TaskRegistry.get()
763
-
764
- for params in default:
765
- registry.set_default_parameters(params)
766
-
767
- gb = graph.GraphBuilder(registry, manifest)
768
- dag = gb.build(task)
769
-
770
- available_in_cache = [
771
- (t.is_available_locally(acache) or (
772
- t.is_available_remotely(acache) and acache.download_enabled()), t)
773
- for t in dag.tasks if t.is_cacheable()]
774
-
775
- for available, task in available_in_cache:
776
- raise_task_error_if(
777
- not remove and not available, task,
778
- "Task artifact is not available in any cache, build it first")
779
-
780
- for task in dag.tasks:
781
- if task.is_resource() or not task.is_cacheable():
782
- continue
783
- manifest_task = manifest.find_task(task)
784
- if remove and manifest_task:
785
- manifest.remove_task(manifest_task)
786
- continue
787
- if not remove:
788
- if not manifest_task:
789
- manifest_task = manifest.create_task()
790
- manifest_task.name = task.qualified_name
791
- manifest_task.identity = task.identity
792
-
793
- manifest.write(fs.path.join(JoltLoader.get().joltdir, output))
824
+ finally:
825
+ queue.shutdown()
794
826
 
795
827
 
796
828
  @cli.command(name="list")
@@ -813,6 +845,8 @@ def _list(ctx, task=None, all=False, reverse=None):
813
845
 
814
846
  raise_error_if(not task and reverse, "TASK required with --reverse")
815
847
 
848
+ options = JoltOptions()
849
+ acache = cache.ArtifactCache.get(options)
816
850
  registry = TaskRegistry.get()
817
851
 
818
852
  if not task:
@@ -826,7 +860,7 @@ def _list(ctx, task=None, all=False, reverse=None):
826
860
  reverse = [utils.stable_task_name(t) for t in utils.as_list(reverse or [])]
827
861
 
828
862
  try:
829
- dag = graph.GraphBuilder(registry, ctx.obj["manifest"]).build(task, influence=False)
863
+ dag = graph.GraphBuilder(registry, acache).build(task, influence=False)
830
864
  except JoltError as e:
831
865
  raise e
832
866
  except Exception:
@@ -860,16 +894,21 @@ def _log(follow, delete):
860
894
  Display the Jolt log file.
861
895
 
862
896
  """
897
+ if not log.logfiles:
898
+ print("No logs exist")
899
+ return
900
+
863
901
  if follow:
864
- subprocess.call("tail -f {0}".format(logfile), shell=True)
902
+ subprocess.call("tail -f {0}".format(log.logfiles[-1]), shell=True)
865
903
  elif delete:
866
- fs.unlink(logfile)
904
+ for file in log.logfiles:
905
+ fs.unlink(file)
867
906
  else:
868
907
  t = tools.Tools()
869
- configured_pager = config.get("jolt", "pager", environ.get("PAGER", None))
908
+ configured_pager = config.get("jolt", "pager", os.environ.get("PAGER", None))
870
909
  for pager in [configured_pager, "less", "more", "cat"]:
871
910
  if pager and t.which(pager):
872
- return subprocess.call("{1} {0}".format(logfile, pager), shell=True)
911
+ return subprocess.call("{1} {0}".format(log.logfiles[-1], pager), shell=True)
873
912
  print(t.read_file(logfile))
874
913
 
875
914
 
@@ -921,9 +960,8 @@ def inspect(ctx, task, influence=False, artifact=False, salt=None):
921
960
 
922
961
  print()
923
962
  print(" Requirements")
924
- manifest = ctx.obj["manifest"]
925
963
  try:
926
- task = task_registry.get_task(task_name, manifest=manifest)
964
+ task = task_registry.get_task(task_name)
927
965
  for req in sorted(utils.as_list(utils.call_or_return(task, task.requires))):
928
966
  print(" {0}".format(task.tools.expand(req)))
929
967
  if not task.requires:
@@ -943,8 +981,9 @@ def inspect(ctx, task, influence=False, artifact=False, salt=None):
943
981
  task.taint = salt
944
982
 
945
983
  if artifact:
984
+ options = JoltOptions(salt=salt)
946
985
  acache = cache.ArtifactCache.get()
947
- builder = graph.GraphBuilder(task_registry, manifest)
986
+ builder = graph.GraphBuilder(task_registry, acache, options)
948
987
  dag = builder.build([task.qualified_name])
949
988
  tasks = dag.select(lambda graph, node: node.task is task)
950
989
  assert len(tasks) == 1, "graph produced multiple tasks, one expected"
@@ -953,14 +992,14 @@ def inspect(ctx, task, influence=False, artifact=False, salt=None):
953
992
 
954
993
  print(" Cache")
955
994
  print(" Identity {0}".format(proxy.identity))
956
- if acache.is_available_locally(proxy):
957
- with acache.get_artifact(proxy) as artifact:
995
+ if proxy.is_available_locally():
996
+ for artifact in filter(lambda a: not a.is_session(), proxy.artifacts):
958
997
  print(" Location {0}".format(artifact.path))
959
998
  print(" Local True ({0})".format(
960
- utils.as_human_size(acache.get_artifact(proxy).get_size())))
999
+ utils.as_human_size(sum([artifact.get_size() for artifact in proxy.artifacts]))))
961
1000
  else:
962
1001
  print(" Local False")
963
- print(" Remote {0}".format(acache.is_available_remotely(proxy)))
1002
+ print(" Remote {0}".format(proxy.is_available_remotely(cache=False)))
964
1003
  print()
965
1004
 
966
1005
  if influence:
@@ -1008,10 +1047,10 @@ def _export(ctx, task):
1008
1047
  executors = scheduler.ExecutorRegistry.get()
1009
1048
  strategy = scheduler.LocalStrategy(executors, acache)
1010
1049
 
1011
- dag = graph.GraphBuilder(registry, ctx.obj["manifest"])
1050
+ dag = graph.GraphBuilder(registry, acache)
1012
1051
  dag = dag.build(task)
1013
1052
 
1014
- gp = graph.GraphPruner(strategy)
1053
+ gp = graph.GraphPruner(acache, strategy)
1015
1054
  dag = gp.prune(dag)
1016
1055
 
1017
1056
  class Export(object):
@@ -1036,17 +1075,97 @@ def _export(ctx, task):
1036
1075
  context = Context(tasks)
1037
1076
 
1038
1077
  for task in context.tasks:
1039
- artifact = acache.get_artifact(task)
1040
- raise_task_error_if(
1041
- artifact.is_temporary(), task,
1042
- "Task artifact not found in local cache, build it first")
1078
+ for artifact in task.artifacts:
1079
+ raise_task_error_if(
1080
+ not task.is_resource() and artifact.is_temporary(), task,
1081
+ "Task artifact not found in local cache, build it first")
1043
1082
 
1044
- visitor = Export()
1045
- cache.visit_artifact(task, artifact, visitor)
1046
- context.add_export(task, visitor)
1083
+ visitor = Export()
1084
+ cache.visit_artifact(task, artifact, visitor)
1085
+ context.add_export(task, visitor)
1047
1086
 
1048
1087
  script = utils.render(
1049
1088
  "export.sh.template",
1050
1089
  ctx=context)
1051
1090
 
1052
1091
  print(script)
1092
+
1093
+
1094
+ @cli.command(name="report")
1095
+ @click.pass_context
1096
+ @hooks.cli_report
1097
+ def _report(ctx):
1098
+ """Create and publish a report with system information and logs.
1099
+
1100
+ The purpose of the report command is to aid troubleshooting by
1101
+ providing users a method to quickly generate and share a report
1102
+ with information about the environment and previous activity.
1103
+
1104
+ The report is uploaded to remote caches if configured, otherwise
1105
+ a report archive is created in the current working directory.
1106
+ """
1107
+
1108
+ options = JoltOptions()
1109
+ acache = cache.ArtifactCache.get(options)
1110
+
1111
+ with tools.Tools() as t:
1112
+
1113
+ artifact = cache.Artifact(
1114
+ acache,
1115
+ None,
1116
+ identity=str(uuid.uuid4()),
1117
+ name="jolt",
1118
+ session=True,
1119
+ tools=t)
1120
+
1121
+ with t.tmpdir("report") as tmp, t.cwd(tmp):
1122
+ log.info("Collecting environment")
1123
+ env = ""
1124
+ for key, val in os.environ.items():
1125
+ env += f"{key} = {val}\n"
1126
+ t.write_file("environ.txt", env, expand=False)
1127
+
1128
+ log.info("Collecting configuration")
1129
+ config.save(tmp)
1130
+ artifact.collect("*.conf", "configs/")
1131
+
1132
+ log.info("Collecting platform information")
1133
+ uname = platform.uname()
1134
+ libc = " ".join(platform.libc_ver())
1135
+ pyver = platform.python_version()
1136
+ pyimpl = platform.python_implementation()
1137
+ pfm = f"""System: {uname.system}
1138
+ Node: {uname.node}
1139
+ Release: {uname.release}
1140
+ Version: {uname.version}
1141
+ Machine: {uname.machine}
1142
+ Libc: {libc}
1143
+ Python: {pyimpl} {pyver}
1144
+ """
1145
+ t.write_file("platform.txt", pfm, expand=False)
1146
+
1147
+ def collect_command(what, cmd):
1148
+ try:
1149
+ log.info("Collecting {}", what)
1150
+ t.run(cmd, output=False)
1151
+ except Exception:
1152
+ pass
1153
+
1154
+ collect_command("pip modules", "pip list > packages-pip.txt")
1155
+ if uname.system == "Linux":
1156
+ collect_command("apt packages", "apt list --installed > packages-apt.txt")
1157
+ artifact.collect("/etc/os-release", "os-release.txt")
1158
+ artifact.collect("*.txt")
1159
+
1160
+ with t.cwd(log.logpath):
1161
+ artifact.collect("*.log", "logs/")
1162
+
1163
+ acache.commit(artifact)
1164
+ if acache.upload_enabled():
1165
+ assert acache.upload(artifact)
1166
+ url = acache.location(artifact)
1167
+ else:
1168
+ url = f"{artifact.identity}.tgz"
1169
+ t.archive(artifact.path, url)
1170
+
1171
+ log.info("Location: {}", url)