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/paths.py CHANGED
@@ -16,6 +16,12 @@ class PathVariable(ArtifactStringAttribute):
16
16
  def unapply(self, task, artifact):
17
17
  pass
18
18
 
19
+ def __bool__(self):
20
+ return bool(str(self))
21
+
22
+ def __eq__(self, value: str) -> bool:
23
+ return str(self) == str(value)
24
+
19
25
  def __str__(self):
20
26
  if self._value is None:
21
27
  return ""
@@ -30,6 +36,10 @@ class PathVariableSet(ArtifactAttributeSet):
30
36
  def create(self, name):
31
37
  return PathVariable(self._artifact, name)
32
38
 
39
+ def __getattr__(self, name):
40
+ attributes = self._get_attributes()
41
+ return attributes.get(name, None)
42
+
33
43
 
34
44
  @ArtifactAttributeSetProvider.Register
35
45
  class PathVariableSetProvider(ArtifactAttributeSetProvider):
@@ -41,7 +51,7 @@ class PathVariableSetProvider(ArtifactAttributeSetProvider):
41
51
  return
42
52
 
43
53
  for key, value in content["paths"].items():
44
- getattr(artifact.paths, key).set_value(value, expand=False)
54
+ setattr(artifact.paths, key, value)
45
55
 
46
56
  def format(self, artifact, content):
47
57
  if "paths" not in content:
@@ -0,0 +1,219 @@
1
+ from jolt import attributes
2
+ from jolt import filesystem as fs
3
+ from jolt import log
4
+ from jolt import utils
5
+ from jolt.error import raise_error_if
6
+
7
+
8
+ class PkgConfigHelper(object):
9
+
10
+ TEMPLATE_PKGCONFIG = """
11
+ prefix={{ artifact.final_path }}
12
+
13
+ Name: {{ pkgname }}
14
+ Description: {{ pkgname }} package
15
+ Version: {{ artifact.identity }}
16
+
17
+ Cflags: {% for flag in cxxflags %}{{ flag }} {% endfor %}{% for inc in incpaths %}-I${prefix}/{{ inc }} {% endfor %}{% for macro in macros %}-D{{ macro }} {% endfor %}
18
+
19
+ Libs: {% for flag in ldflags %}{{ flag }} {% endfor %}{% for libpath in libpaths %}-L${prefix}/{{ libpath }} {% endfor %}{% for library in libraries %}-l{{ library }} {% endfor %}
20
+
21
+ """
22
+
23
+ def __init__(self, artifact, tools):
24
+ self.artifact = artifact
25
+ self.tools = tools
26
+
27
+ def _mkpath(self, path):
28
+ if fs.path.commonpath([path, self.artifact.path]) != self.artifact.path:
29
+ return path
30
+ return fs.path.relpath(path, self.artifact.path)
31
+
32
+ def cflags(self, package):
33
+ package = " ".join(utils.as_list(package))
34
+ try:
35
+ with self.tools.environ(**self.environ):
36
+ output = self.tools.run("{} --maximum-traverse-depth=1 --cflags-only-other {}", self.pkgconfig, package, output=False)
37
+ return output.strip().split()
38
+ except Exception as e:
39
+ log.debug("PkgConfig.cflags: {}", str(e))
40
+ return []
41
+
42
+ def incpaths(self, package):
43
+ package = " ".join(utils.as_list(package))
44
+ try:
45
+ with self.tools.environ(**self.environ):
46
+ output = self.tools.run("{} --maximum-traverse-depth=1 --cflags-only-I {}", self.pkgconfig, package, output=False)
47
+ return [self._mkpath(inc[2:]) for inc in output.strip().split()]
48
+ except Exception as e:
49
+ log.debug("PkgConfig.incpaths: {}", str(e))
50
+ return []
51
+
52
+ def linkflags(self, package):
53
+ package = " ".join(utils.as_list(package))
54
+ try:
55
+ with self.tools.environ(**self.environ):
56
+ output = self.tools.run("{} --maximum-traverse-depth=1 --libs-only-other {}", self.pkgconfig, package, output=False)
57
+ return output.strip().split()
58
+ except Exception as e:
59
+ log.debug("PkgConfig.linkflags: {}", str(e))
60
+ return []
61
+
62
+ def libpaths(self, package):
63
+ package = " ".join(utils.as_list(package))
64
+ try:
65
+ with self.tools.environ(**self.environ):
66
+ output = self.tools.run("{} --maximum-traverse-depth=1 --libs-only-L {}", self.pkgconfig, package, output=False)
67
+ return [self._mkpath(lib[2:]) for lib in output.strip().split()]
68
+ except Exception as e:
69
+ log.debug("PkgConfig.libpaths: {}", str(e))
70
+ return []
71
+
72
+ def libraries(self, package):
73
+ package = " ".join(utils.as_list(package))
74
+ try:
75
+ with self.tools.environ(**self.environ):
76
+ output = self.tools.run("{} --maximum-traverse-depth=1 --libs-only-l {}", self.pkgconfig, package, output=False)
77
+ return [lib[2:] for lib in output.strip().split()]
78
+ except Exception as e:
79
+ log.debug("PkgConfig.libraries: {}", str(e))
80
+ return []
81
+
82
+ def write_pc(self, package):
83
+ with self.tools.tmpdir() as tmpdir, self.tools.cwd(tmpdir):
84
+ content = self.tools.render(
85
+ self.TEMPLATE_PKGCONFIG,
86
+ artifact=self.artifact,
87
+ pkgname=package,
88
+ macros=list(self.artifact.cxxinfo.macros),
89
+ incpaths=list(self.artifact.cxxinfo.incpaths),
90
+ libpaths=list(self.artifact.cxxinfo.libpaths),
91
+ libraries=list(self.artifact.cxxinfo.libraries),
92
+ )
93
+ print(content)
94
+ self.tools.write_file(f"{package}.pc", content, expand=False)
95
+ self.artifact.collect(f"{package}.pc", "lib/pkgconfig/")
96
+
97
+ @property
98
+ def pkgconfig(self):
99
+ pkgconf = self.tools.which(self.tools.getenv("PKG_CONFIG", "pkg-config"))
100
+ raise_error_if(not pkgconf, "No pkg-config tool found in PATH")
101
+ return pkgconf
102
+
103
+ @property
104
+ def environ(self):
105
+ path = self.artifact.environ.get("PKG_CONFIG_PATH")
106
+ if path is None:
107
+ self.tools._task.verbose("No PKG_CONFIG_PATH in artifact environment")
108
+
109
+ # Path from the artifact environment
110
+ path = str(path).split(fs.pathsep)
111
+ path = fs.pathsep.join(fs.path.join(self.artifact.path, p) for p in path)
112
+
113
+ # Append existing PKG_CONFIG_PATH from the tools environment
114
+ if self.tools.getenv("PKG_CONFIG_PATH"):
115
+ path = path + fs.pathsep + self.tools.getenv("PKG_CONFIG_PATH")
116
+
117
+ return {"PKG_CONFIG_PATH": path}
118
+
119
+
120
+ def to_cxxinfo(
121
+ pkg: list | str,
122
+ cflags: bool = True,
123
+ cxxflags: bool = True,
124
+ incpaths: bool = True,
125
+ ldflags: bool = True,
126
+ libpaths: bool = True,
127
+ libraries: bool = True,
128
+ ):
129
+ """
130
+ Decorator to add pkg-config information to cxxinfo metadata of an artifact.
131
+
132
+ The decorator enables interoperability between libraries built with Jolt's
133
+ Ninja plugin and external packages that provide pkg-config files.
134
+
135
+ It uses the pkg-config tool to query for compiler and linker flags,
136
+ include paths, library paths, and libraries associated with a given package.
137
+ If the relevant flags are found, they are appended to the corresponding fields
138
+ in the artifact's cxxinfo metadata.
139
+
140
+ Args:
141
+ pkg (str): The name of the pkg-config package to be added to cxxinfo.
142
+ cflags (bool): Whether to add C compiler flags from pkg-config.
143
+ cxxflags (bool): Whether to add C++ compiler flags from pkg-config.
144
+ incpaths (bool): Whether to add include paths from pkg-config.
145
+ ldflags (bool): Whether to add linker flags from pkg-config.
146
+ libpaths (bool): Whether to add library paths from pkg-config.
147
+ libraries (bool): Whether to add libraries from pkg-config.
148
+ """
149
+
150
+ def decorate(cls):
151
+ original_publish = cls.publish
152
+
153
+ def publish(self, artifact, tools):
154
+ original_publish(self, artifact, tools)
155
+
156
+ pc = PkgConfigHelper(artifact, tools)
157
+ if not pc.environ:
158
+ self.verbose("Skipping pkg-config cxxinfo addition due to missing PKG_CONFIG_PATH.")
159
+ return
160
+
161
+ if cflags:
162
+ artifact.cxxinfo.cflags.extend(pc.cflags(pkg))
163
+ if cxxflags:
164
+ artifact.cxxinfo.cxxflags.extend(pc.cflags(pkg))
165
+ if incpaths:
166
+ artifact.cxxinfo.incpaths.extend(pc.incpaths(pkg))
167
+ if ldflags:
168
+ artifact.cxxinfo.ldflags.extend(pc.linkflags(pkg))
169
+ if libpaths:
170
+ artifact.cxxinfo.libpaths.extend(pc.libpaths(pkg))
171
+ if libraries:
172
+ artifact.cxxinfo.libraries.extend(pc.libraries(pkg))
173
+
174
+ cls.publish = publish
175
+ return cls
176
+
177
+ return decorate
178
+
179
+
180
+ def from_cxxinfo(package):
181
+ """
182
+ Decorator to write a pkg-config file for the given package
183
+ based on the cxxinfo metadata of the artifact.
184
+ """
185
+
186
+ def decorate(cls):
187
+ original_publish = cls.publish
188
+ original_unpack = cls.unpack
189
+
190
+ def publish(self, artifact, tools):
191
+ original_publish(self, artifact, tools)
192
+
193
+ pc = PkgConfigHelper(artifact, tools)
194
+ pc.write_pc(package)
195
+
196
+ def unpack(self, artifact, tools):
197
+ original_unpack(self, artifact, tools)
198
+
199
+ pc = PkgConfigHelper(artifact, tools)
200
+ pc.write_pc(package)
201
+
202
+ cls.publish = publish
203
+ cls.unpack = unpack
204
+ return cls
205
+
206
+ return decorate
207
+
208
+
209
+ def requires():
210
+ """ Decorator to add pkg-config requirements to a task. """
211
+
212
+ import jolt.pkgs.pkgconfig
213
+
214
+ def decorate(cls):
215
+ cls = attributes.requires("requires_pkgconf")(cls)
216
+ cls.requires_pkgconf = ["pkgconf"]
217
+ return cls
218
+
219
+ return decorate
jolt/plugins/podman.py CHANGED
@@ -1,4 +1,4 @@
1
- from jolt import Download, Parameter, Resource, Task
1
+ from jolt import Parameter, Resource, Task
2
2
  from jolt.error import raise_task_error_if
3
3
  from jolt.tasks import TaskRegistry
4
4
  from jolt import attributes
@@ -15,7 +15,6 @@ from jolt.cache import ArtifactAttributeSetProvider
15
15
  import contextlib
16
16
  import json
17
17
  from os import path
18
- from platform import system
19
18
  import tarfile
20
19
 
21
20
 
@@ -25,24 +24,20 @@ class PodmanListVariable(ArtifactListAttribute):
25
24
 
26
25
  class PodmanImportListVariable(PodmanListVariable):
27
26
  def apply(self, task, artifact):
28
- if isinstance(task, Resource):
29
- return
30
27
  for tar in self.items():
31
28
  try:
32
29
  tag = artifact.podman.tags[0]
33
30
  except IndexError:
34
- tag = "{}:{}".format(artifact.get_task().canonical_name, artifact.identity)
31
+ tag = artifact.tools.expand("{canonical_name}:{identity}")
35
32
  task.tools.run(
36
33
  "podman import {} {}",
37
- fs.path.join(artifact.path, tar, tag), output_on_error=True)
34
+ fs.path.join(artifact.path, tar), tag, output_on_error=True)
38
35
  for extra_tag in artifact.podman.tags[1:]:
39
36
  task.tools.run("podman tag {} {}", tag, extra_tag, output_on_error=True)
40
37
 
41
38
 
42
39
  class PodmanLoadListVariable(PodmanListVariable):
43
40
  def apply(self, task, artifact):
44
- if isinstance(task, Resource):
45
- return
46
41
  for image in self.items():
47
42
  task.tools.run(
48
43
  "podman load -i {}",
@@ -51,16 +46,12 @@ class PodmanLoadListVariable(PodmanListVariable):
51
46
 
52
47
  class PodmanPullListVariable(PodmanListVariable):
53
48
  def apply(self, task, artifact):
54
- if isinstance(task, Resource):
55
- return
56
49
  for image in self.items():
57
50
  task.tools.run("podman pull {}", image, output_on_error=True)
58
51
 
59
52
 
60
53
  class PodmanRmiListVariable(PodmanListVariable):
61
54
  def unapply(self, task, artifact):
62
- if isinstance(task, Resource):
63
- return
64
55
  for image in self.items():
65
56
  task.tools.run("podman rmi -f {}", image, output_on_error=True)
66
57
 
@@ -108,35 +99,6 @@ class PodmanAttributeProvider(ArtifactAttributeSetProvider):
108
99
  artifact.podman.unapply(task, artifact)
109
100
 
110
101
 
111
- class PodmanClient(Download):
112
- """ Task: Downloads and publishes the Podman command line client.
113
-
114
- The task will be automatically made available after importing
115
- ``jolt.plugins.podman``.
116
- """
117
-
118
- name = "podman/cli"
119
- """ Name of the task """
120
-
121
- arch = Parameter("x86_64", help="Host architecture")
122
- """ Host architecture [x86_64] """
123
-
124
- collect = ["podman/podman"]
125
-
126
- host = Parameter(system().lower(), help="Host operating system")
127
- """ Host operating system [autodetected] """
128
-
129
- url = "https://download.podman.com/{host}/static/stable/{arch}/podman-{version}.tgz"
130
- """ URL of binaries """
131
-
132
- version = Parameter("20.10.13", help="Podman version")
133
- """ Podman version [20.10.13] """
134
-
135
- def publish(self, artifact, tools):
136
- super().publish(artifact, tools)
137
- artifact.environ.PATH.append("podman")
138
-
139
-
140
102
  @attributes.requires("_image")
141
103
  class Container(Resource):
142
104
  """
@@ -193,6 +155,18 @@ class Container(Resource):
193
155
  Alternatively, assign ``True`` to publish all exposed ports to random ports.
194
156
  """
195
157
 
158
+ release_on_error = True
159
+ """ Stop and remove container on error to avoid resource leaks. """
160
+
161
+ stop_timeout = 10
162
+ """ Timeout in seconds for stopping the container .
163
+
164
+ When stopping the container, the task will wait for the container to stop
165
+ for the specified number of seconds before forcefully killing it.
166
+
167
+ Default: 10 seconds.
168
+ """
169
+
196
170
  volumes = []
197
171
  """
198
172
  A list of volumes to mount.
@@ -202,8 +176,8 @@ class Container(Resource):
202
176
  """
203
177
 
204
178
  volumes_default = [
205
- "{joltdir}:{joltdir}",
206
- "{joltcachedir}:{joltcachedir}",
179
+ "{joltdir}",
180
+ "{joltcachedir}",
207
181
  ]
208
182
  """
209
183
  A list of default volumes to mount.
@@ -243,7 +217,7 @@ class Container(Resource):
243
217
  def _image(self):
244
218
  registry = TaskRegistry.get()
245
219
  tool = tools.Tools(self)
246
- if registry.get_task_class(tool.expand(self.image)):
220
+ if registry.has_task(tool.expand(self.image)):
247
221
  return [self.image]
248
222
  return []
249
223
 
@@ -280,10 +254,12 @@ class Container(Resource):
280
254
 
281
255
  @property
282
256
  def _volumes(self):
283
- return " ".join([utils.option("-v ", self.tools.expand(vol))
257
+ return " ".join([utils.option("-v ", self.tools.expand_path(vol))
284
258
  for vol in self.volumes_default + self.volumes])
285
259
 
286
260
  def acquire(self, artifact, deps, tools, owner):
261
+ self._context_stack = None
262
+ self.container = None
287
263
  self.joltcachedir = config.get_cachedir()
288
264
  try:
289
265
  image = deps[self.image]
@@ -292,9 +268,10 @@ class Container(Resource):
292
268
  image = tools.expand(self.image)
293
269
 
294
270
  self._info(f"Creating container from image '{image}'")
295
- self.container = tools.run(
296
- "podman run -i -d {_cap_adds} {_cap_drops} {_entrypoint} {_labels} {_ports} {_privileged} {_user} {_environment} {_volumes} {image} {_arguments}",
297
- image=image, output_on_error=True)
271
+ with utils.delayed_interrupt():
272
+ self.container = tools.run(
273
+ "podman run -i -d {_cap_adds} {_cap_drops} {_entrypoint} {_labels} {_ports} {_privileged} {_user} {_environment} {_volumes} {image} {_arguments}",
274
+ image=image, output_on_error=True)
298
275
 
299
276
  self._info("Created container '{container}'")
300
277
  info = tools.run("podman inspect {container}", output_on_error=True)
@@ -307,14 +284,18 @@ class Container(Resource):
307
284
  owner.tools.runprefix(f"podman exec -i {artifact.container}"))
308
285
 
309
286
  def release(self, artifact, deps, tools, owner):
310
- if self.chroot:
287
+ if self.chroot and self._context_stack:
311
288
  self._context_stack.close()
312
289
 
313
- self._info("Stopping container '{container}'")
314
- tools.run("podman stop {container}", output_on_error=True)
290
+ if not self.container:
291
+ return
315
292
 
316
- self._info("Deleting container '{container}'")
317
- tools.run("podman rm {container}", output_on_error=True)
293
+ try:
294
+ self._info("Stopping container '{container}'")
295
+ tools.run("podman stop -t {stop_timeout} {container}", output_on_error=True)
296
+ finally:
297
+ self._info("Deleting container '{container}'")
298
+ tools.run("podman rm -f {container}", output_on_error=True)
318
299
 
319
300
 
320
301
  class PodmanLogin(Resource):
@@ -333,8 +314,6 @@ class PodmanLogin(Resource):
333
314
  name = "podman/login"
334
315
  """ Name of the resource """
335
316
 
336
- requires = ["podman/cli"]
337
-
338
317
  user = Parameter("", help="Podman Registry username")
339
318
  """
340
319
  Podman Registry username.
@@ -374,7 +353,6 @@ class PodmanLogin(Resource):
374
353
  tools.run("podman logout {server}")
375
354
 
376
355
 
377
- TaskRegistry.get().add_task_class(PodmanClient)
378
356
  TaskRegistry.get().add_task_class(PodmanLogin)
379
357
 
380
358
 
@@ -434,7 +412,6 @@ class ContainerImage(Task):
434
412
 
435
413
  Optionally add requirements to:
436
414
 
437
- - ``podman/cli`` to provision the Podman client, if none is available on the host.
438
415
  - ``podman/login`` to automatically login to the Podman registry.
439
416
 
440
417
  This class must be subclassed.
@@ -457,12 +434,14 @@ class ContainerImage(Task):
457
434
  class Busybox(ContainerImage):
458
435
  \"\"\" Publishes Busybox image as gzip-compressed tarball \"\"\"
459
436
  compression = "gz"
460
- requires = ["podman/cli"]
461
437
  tags = ["busybox:{identity}"]
462
438
 
463
439
  """
464
440
  abstract = True
465
441
 
442
+ annotations = []
443
+ """ A list of image annotations """
444
+
466
445
  autoload = True
467
446
  """
468
447
  Automatically load image file into local registry when the artifact is
@@ -492,10 +471,26 @@ class ContainerImage(Task):
492
471
  dockerfile = "Dockerfile"
493
472
  """ Path to the Dockerfile to build, or the full source code of such a file. """
494
473
 
495
- output = "oci-archive"
496
- """ none, oci-archive, oci-directory, docker-archive, directory, tar, tar.gz, tgz, tar.bz2, tar.xz"""
474
+ output = ["oci-archive"]
475
+ """
476
+ List of output formats.
477
+
478
+ If set to None, no output is produced and published.
479
+
480
+ Supported formats:
481
+ - archive
482
+ - cpio
483
+ - custom
484
+ - directory
485
+ - docker-archive
486
+ - ext4
487
+ - oci-archive
488
+ - oci-directory
489
+ - squashfs
497
490
 
498
- imagefile = "{canonical_name}.tar"
491
+ """
492
+
493
+ imagefile = "{canonical_name}"
499
494
  """
500
495
  Name of the image tarball published by the task.
501
496
 
@@ -507,9 +502,6 @@ class ContainerImage(Task):
507
502
  labels = []
508
503
  """ A list of image metadata labels """
509
504
 
510
- platform = None
511
- """ Target platform, e.g. linux/arm/v7. """
512
-
513
505
  pull = True
514
506
  """
515
507
  Always pull images when building.
@@ -525,15 +517,25 @@ class ContainerImage(Task):
525
517
  The ``podman/login`` Jolt resource can be used for that purpose.
526
518
  """
527
519
 
520
+ size = None
521
+ """ Size of the image, e.g. "64M" (for certain output formats). """
522
+
528
523
  squash = False
529
524
  """ Squash image layers """
530
525
 
531
526
  tags = ["{canonical_name}:{identity}"]
532
527
  """ Optional list of image tags. Defaults to task's canonical name. """
533
528
 
529
+ target = None
530
+ """ Target platform, e.g. linux/arm/v7. """
531
+
534
532
  def __init__(self, *args, **kwargs):
535
533
  super().__init__(*args, **kwargs)
536
534
 
535
+ @property
536
+ def _annotations(self):
537
+ return " ".join([utils.option("--annotation ", self.tools.expand(an)) for an in self.annotations])
538
+
537
539
  @property
538
540
  def _buildargs(self):
539
541
  return " ".join([utils.option("--build-arg ", self.tools.expand(ba)) for ba in self.buildargs])
@@ -542,9 +544,13 @@ class ContainerImage(Task):
542
544
  def _labels(self):
543
545
  return " ".join([utils.option("-l ", self.tools.expand(label)) for label in self.labels])
544
546
 
547
+ @property
548
+ def _output(self):
549
+ return utils.as_list(self.output) if self.output else []
550
+
545
551
  @property
546
552
  def _platform(self):
547
- platform = self.tools.expand(self.platform) if self.platform else None
553
+ platform = self.tools.expand(self.target) if self.target else None
548
554
  return utils.option("--platform ", platform)
549
555
 
550
556
  @property
@@ -575,7 +581,7 @@ class ContainerImage(Task):
575
581
  tools.expand_relpath(context))
576
582
 
577
583
  with tools.cwd(context):
578
- tools.run("podman build {_platform} . -f {} {_buildargs} {_labels} {_tags} {pull}{squash}",
584
+ tools.run("podman build {_platform} . -f {} {_annotations} {_buildargs} {_labels} {_tags} {pull}{squash}",
579
585
  utils.quote(dockerfile), pull=pull, squash=squash)
580
586
 
581
587
  try:
@@ -584,22 +590,34 @@ class ContainerImage(Task):
584
590
  for tag in self.tags:
585
591
  tools.run("podman push {}", tag)
586
592
 
587
- if self.output:
588
- self.info("Saving image")
589
- with tools.cwd(tools.builddir()):
590
- if self.output in ["oci-archive", "docker-archive"]:
593
+ for output in self._output:
594
+ self.info("Saving image as {}", output)
595
+ outdir = tools.builddir(output)
596
+ with tools.cwd(outdir):
597
+ if output in ["oci-archive", "docker-archive"]:
591
598
  tools.run("podman image save --format={output} {} -o {}", self.tags[0], "image.tar")
592
- if self.output == "oci-directory":
599
+ if output == "oci-directory":
593
600
  tools.run("podman image save --format=oci-dir {} -o {}", self.tags[0], "image.dir")
594
- if self.output in ["archive", "directory"]:
601
+ if output in ["archive", "cpio", "custom", "directory", "ext4", "squashfs"]:
595
602
  ctr = tools.run("podman create {}", self.tags[0])
596
603
  try:
597
- mount_path = tools.run("podman unshare podman mount {}", ctr, output_on_error=True)
598
- if self.output == "archive":
599
- tools.run("podman unshare tar -C {} -cf image.tar .", mount_path, output_on_error=True)
600
- else:
601
- tools.mkdir("image.dir")
602
- tools.run("podman unshare tar c -C {} . | tar --no-same-permissions --no-same-owner --no-overwrite-dir -x -C ./image.dir/", mount_path, output_on_error=True)
604
+ with tools.runprefix("podman unshare "):
605
+ mount_path = tools.run("podman mount {}", ctr, output_on_error=True)
606
+ if output == "custom":
607
+ self.run_custom(deps, tools, mount_path)
608
+ elif output == "archive":
609
+ tools.run("tar -C {} -cf image.tar .", mount_path, output_on_error=True)
610
+ elif output == "cpio":
611
+ with tools.cwd(mount_path):
612
+ tools.run("find | podman unshare cpio -o -F {}/image.cpio -H newc", outdir, output_on_error=True)
613
+ elif output == "ext4":
614
+ assert self.size, "Size must be set for ext4 output"
615
+ tools.run("mke2fs -t ext4 -F -L rootfs -d {} image.ext4 {size}", mount_path, output_on_error=True)
616
+ elif output == "squashfs":
617
+ tools.run("mksquashfs {} image.squashfs", mount_path, output_on_error=True)
618
+ else:
619
+ tools.mkdir("image.dir")
620
+ tools.run("tar c -C {} . | tar --no-same-permissions --no-same-owner --no-overwrite-dir -x -C ./image.dir/", mount_path, output_on_error=True)
603
621
  finally:
604
622
  utils.call_and_catch(tools.run, "podman rm {}", ctr)
605
623
  finally:
@@ -608,26 +626,52 @@ class ContainerImage(Task):
608
626
  for tag in self.tags:
609
627
  utils.call_and_catch(tools.run("podman rmi -f {}", tag))
610
628
 
629
+ def run_custom(self, deps, tools, mount_path):
630
+ """
631
+ Save image as custom output format.
632
+
633
+ The method is called when the output format is set to "custom".
634
+ The mount_path is the path to the mounted container root filesystem.
635
+
636
+ The default implementation does nothing.
637
+ """
638
+ pass
639
+
611
640
  def publish(self, artifact, tools):
641
+ """ Publish the image as different output formats """
642
+
612
643
  artifact.strings.tag = tools.expand(self.tags[0])
613
644
 
614
645
  for tag in self.tags:
615
646
  artifact.podman.tags.append(tag)
616
647
 
617
- if self.output:
618
- with tools.cwd(tools.builddir()):
619
- if self.output in ["oci-archive", "docker-archive"] and self._imagefile:
620
- artifact.collect("image.tar", "{_imagefile}")
648
+ for output in self._output:
649
+ outdir = tools.builddir(output)
650
+ with tools.cwd(outdir):
651
+ if output in ["oci-archive", "docker-archive"] and self._imagefile:
652
+ artifact.collect("image.tar", output + "/{_imagefile}.tar")
621
653
  if self._autoload:
622
- artifact.podman.load.append("{_imagefile}")
623
- artifact.podman.rmi.append(artifact.strings.tag.get_value())
624
- if self.output in ["archive"] and self._imagefile:
625
- artifact.collect("image.tar", "{_imagefile}")
654
+ artifact.podman.load.append(output + "/{_imagefile}.tar")
655
+ artifact.podman.rmi.append(artifact.strings.tag)
656
+ if output in ["archive"] and self._imagefile:
657
+ artifact.collect("image.tar", output + "/{_imagefile}.tar")
626
658
  if self._autoload:
627
- artifact.podman.imprt.append("{_imagefile}")
628
- artifact.podman.rmi.append(artifact.strings.tag.get_value())
629
- if self.output in ["directory", "oci-directory"]:
659
+ artifact.podman.imprt.append(output + "/{_imagefile}.tar")
660
+ artifact.podman.rmi.append(artifact.strings.tag)
661
+ if output in ["directory", "oci-directory"]:
630
662
  with tools.cwd("image.dir"):
631
- artifact.collect("*", symlinks=True)
632
- if self.output in ["directory"]:
633
- artifact.paths.rootfs = "."
663
+ artifact.collect("*", f"{output}/", symlinks=True)
664
+ if output in ["cpio"]:
665
+ artifact.collect("image.cpio", output + "/{_imagefile}.cpio")
666
+ if output in ["custom"]:
667
+ self.publish_custom(artifact, tools)
668
+ if output in ["directory"]:
669
+ artifact.paths.rootfs = output
670
+ if output in ["ext4"]:
671
+ artifact.collect("image.ext4", output + "/{_imagefile}.ext4")
672
+ if output in ["squashfs"]:
673
+ artifact.collect("image.squashfs", output + "/{_imagefile}.squashfs")
674
+
675
+ def publish_custom(self, artifact, tools):
676
+ """ Publish custom output as produced by run_custom """
677
+ pass