jolt 0.9.342__py3-none-any.whl → 0.9.429__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. jolt/__init__.py +47 -0
  2. jolt/cache.py +358 -159
  3. jolt/cli.py +71 -104
  4. jolt/config.py +14 -26
  5. jolt/filesystem.py +2 -2
  6. jolt/graph.py +56 -28
  7. jolt/influence.py +67 -2
  8. jolt/loader.py +150 -186
  9. jolt/log.py +12 -2
  10. jolt/manifest.py +0 -46
  11. jolt/options.py +35 -12
  12. jolt/pkgs/abseil.py +42 -0
  13. jolt/pkgs/asio.py +25 -0
  14. jolt/pkgs/autoconf.py +41 -0
  15. jolt/pkgs/automake.py +41 -0
  16. jolt/pkgs/b2.py +31 -0
  17. jolt/pkgs/boost.py +111 -0
  18. jolt/pkgs/boringssl.py +32 -0
  19. jolt/pkgs/busybox.py +39 -0
  20. jolt/pkgs/bzip2.py +43 -0
  21. jolt/pkgs/cares.py +29 -0
  22. jolt/pkgs/catch2.py +36 -0
  23. jolt/pkgs/cbindgen.py +17 -0
  24. jolt/pkgs/cista.py +19 -0
  25. jolt/pkgs/clang.py +44 -0
  26. jolt/pkgs/cli11.py +23 -0
  27. jolt/pkgs/cmake.py +48 -0
  28. jolt/pkgs/cpython.py +196 -0
  29. jolt/pkgs/crun.py +29 -0
  30. jolt/pkgs/curl.py +38 -0
  31. jolt/pkgs/dbus.py +18 -0
  32. jolt/pkgs/double_conversion.py +24 -0
  33. jolt/pkgs/fastfloat.py +21 -0
  34. jolt/pkgs/ffmpeg.py +28 -0
  35. jolt/pkgs/flatbuffers.py +29 -0
  36. jolt/pkgs/fmt.py +27 -0
  37. jolt/pkgs/fstree.py +20 -0
  38. jolt/pkgs/gflags.py +18 -0
  39. jolt/pkgs/glib.py +18 -0
  40. jolt/pkgs/glog.py +25 -0
  41. jolt/pkgs/glslang.py +21 -0
  42. jolt/pkgs/golang.py +16 -11
  43. jolt/pkgs/googlebenchmark.py +18 -0
  44. jolt/pkgs/googletest.py +46 -0
  45. jolt/pkgs/gperf.py +15 -0
  46. jolt/pkgs/grpc.py +73 -0
  47. jolt/pkgs/hdf5.py +19 -0
  48. jolt/pkgs/help2man.py +14 -0
  49. jolt/pkgs/inja.py +28 -0
  50. jolt/pkgs/jsoncpp.py +31 -0
  51. jolt/pkgs/libarchive.py +43 -0
  52. jolt/pkgs/libcap.py +44 -0
  53. jolt/pkgs/libdrm.py +44 -0
  54. jolt/pkgs/libedit.py +42 -0
  55. jolt/pkgs/libevent.py +31 -0
  56. jolt/pkgs/libexpat.py +27 -0
  57. jolt/pkgs/libfastjson.py +21 -0
  58. jolt/pkgs/libffi.py +16 -0
  59. jolt/pkgs/libglvnd.py +30 -0
  60. jolt/pkgs/libogg.py +28 -0
  61. jolt/pkgs/libpciaccess.py +18 -0
  62. jolt/pkgs/libseccomp.py +21 -0
  63. jolt/pkgs/libtirpc.py +24 -0
  64. jolt/pkgs/libtool.py +42 -0
  65. jolt/pkgs/libunwind.py +35 -0
  66. jolt/pkgs/libva.py +18 -0
  67. jolt/pkgs/libvorbis.py +33 -0
  68. jolt/pkgs/libxml2.py +35 -0
  69. jolt/pkgs/libxslt.py +17 -0
  70. jolt/pkgs/libyajl.py +16 -0
  71. jolt/pkgs/llvm.py +81 -0
  72. jolt/pkgs/lua.py +54 -0
  73. jolt/pkgs/lz4.py +26 -0
  74. jolt/pkgs/m4.py +14 -0
  75. jolt/pkgs/make.py +17 -0
  76. jolt/pkgs/mesa.py +81 -0
  77. jolt/pkgs/meson.py +17 -0
  78. jolt/pkgs/mstch.py +28 -0
  79. jolt/pkgs/mysql.py +60 -0
  80. jolt/pkgs/nasm.py +49 -0
  81. jolt/pkgs/ncurses.py +30 -0
  82. jolt/pkgs/ng_log.py +25 -0
  83. jolt/pkgs/ninja.py +45 -0
  84. jolt/pkgs/nlohmann_json.py +25 -0
  85. jolt/pkgs/nodejs.py +19 -11
  86. jolt/pkgs/opencv.py +24 -0
  87. jolt/pkgs/openjdk.py +26 -0
  88. jolt/pkgs/openssl.py +103 -0
  89. jolt/pkgs/paho.py +76 -0
  90. jolt/pkgs/patchelf.py +16 -0
  91. jolt/pkgs/perl.py +42 -0
  92. jolt/pkgs/pkgconfig.py +64 -0
  93. jolt/pkgs/poco.py +39 -0
  94. jolt/pkgs/protobuf.py +77 -0
  95. jolt/pkgs/pugixml.py +27 -0
  96. jolt/pkgs/python.py +19 -0
  97. jolt/pkgs/qt.py +35 -0
  98. jolt/pkgs/rapidjson.py +26 -0
  99. jolt/pkgs/rapidyaml.py +28 -0
  100. jolt/pkgs/re2.py +30 -0
  101. jolt/pkgs/re2c.py +17 -0
  102. jolt/pkgs/readline.py +15 -0
  103. jolt/pkgs/rust.py +41 -0
  104. jolt/pkgs/sdl.py +28 -0
  105. jolt/pkgs/simdjson.py +27 -0
  106. jolt/pkgs/soci.py +46 -0
  107. jolt/pkgs/spdlog.py +29 -0
  108. jolt/pkgs/spirv_llvm.py +21 -0
  109. jolt/pkgs/spirv_tools.py +24 -0
  110. jolt/pkgs/sqlite.py +83 -0
  111. jolt/pkgs/ssl.py +12 -0
  112. jolt/pkgs/texinfo.py +15 -0
  113. jolt/pkgs/tomlplusplus.py +22 -0
  114. jolt/pkgs/wayland.py +26 -0
  115. jolt/pkgs/x11.py +58 -0
  116. jolt/pkgs/xerces_c.py +20 -0
  117. jolt/pkgs/xorg.py +360 -0
  118. jolt/pkgs/xz.py +29 -0
  119. jolt/pkgs/yamlcpp.py +30 -0
  120. jolt/pkgs/zeromq.py +47 -0
  121. jolt/pkgs/zlib.py +69 -0
  122. jolt/pkgs/zstd.py +33 -0
  123. jolt/plugins/autotools.py +66 -0
  124. jolt/plugins/cmake.py +74 -6
  125. jolt/plugins/conan.py +238 -0
  126. jolt/plugins/cxxinfo.py +7 -0
  127. jolt/plugins/docker.py +3 -3
  128. jolt/plugins/environ.py +11 -0
  129. jolt/plugins/fetch.py +141 -0
  130. jolt/plugins/gdb.py +10 -6
  131. jolt/plugins/git.py +60 -11
  132. jolt/plugins/libtool.py +63 -0
  133. jolt/plugins/linux.py +990 -0
  134. jolt/plugins/meson.py +61 -0
  135. jolt/plugins/ninja-compdb.py +11 -7
  136. jolt/plugins/ninja.py +245 -26
  137. jolt/plugins/paths.py +11 -1
  138. jolt/plugins/pkgconfig.py +219 -0
  139. jolt/plugins/podman.py +15 -41
  140. jolt/plugins/python.py +137 -0
  141. jolt/plugins/rust.py +25 -0
  142. jolt/plugins/scheduler.py +18 -14
  143. jolt/plugins/selfdeploy/setup.py +2 -1
  144. jolt/plugins/selfdeploy.py +21 -30
  145. jolt/plugins/strings.py +19 -10
  146. jolt/scheduler.py +428 -138
  147. jolt/tasks.py +159 -7
  148. jolt/tools.py +105 -51
  149. jolt/utils.py +16 -1
  150. jolt/version.py +1 -1
  151. {jolt-0.9.342.dist-info → jolt-0.9.429.dist-info}/METADATA +64 -9
  152. jolt-0.9.429.dist-info/RECORD +207 -0
  153. {jolt-0.9.342.dist-info → jolt-0.9.429.dist-info}/WHEEL +1 -1
  154. jolt/plugins/debian.py +0 -338
  155. jolt/plugins/repo.py +0 -253
  156. jolt-0.9.342.dist-info/RECORD +0 -93
  157. {jolt-0.9.342.dist-info → jolt-0.9.429.dist-info}/entry_points.txt +0 -0
  158. {jolt-0.9.342.dist-info → jolt-0.9.429.dist-info}/top_level.txt +0 -0
jolt/tasks.py CHANGED
@@ -10,6 +10,7 @@ import platform
10
10
  from threading import RLock
11
11
  import subprocess
12
12
  from os import environ
13
+ from os import sys as os_sys
13
14
  import sys
14
15
  import unittest as ut
15
16
  from urllib.parse import urlparse
@@ -20,6 +21,7 @@ import traceback
20
21
  from jolt import filesystem as fs
21
22
  from jolt import log
22
23
  from jolt import utils
24
+ from jolt.cache import ArtifactAttributeSetProvider
23
25
  from jolt.error import raise_error_if, raise_task_error, raise_task_error_if
24
26
  from jolt.error import raise_unreported_task_error_if
25
27
  from jolt.error import JoltError, JoltCommandError, LoggedJoltError
@@ -135,7 +137,7 @@ class Parameter(object):
135
137
  self.name = name
136
138
 
137
139
  def __init__(self, default=None, values=None, required=True,
138
- const=False, influence=True, help=None):
140
+ const=False, influence=True, help=None, valuesfn=None):
139
141
  """
140
142
  Creates a new parameter.
141
143
 
@@ -143,6 +145,10 @@ class Parameter(object):
143
145
  default (str, optional): An optional default value.
144
146
  values (list, optional): A list of accepted values. An
145
147
  assertion is raised if an unlisted value is assigned to the parameter.
148
+ valuesfn (func, optional); A function that validates the assigned value.
149
+ If both values and valuesfn are specified, the values list is validated
150
+ first. The function is passed the assigned value and should return.
151
+ boolean value. If the function returns False, a ParameterValueError is raised.
146
152
  required (boolean, optional): If required, the parameter must be assigned
147
153
  a value before the task can be executed. The default is ``True``.
148
154
  const (boolean, optional): If const is True, the parameter is immutable
@@ -164,6 +170,7 @@ class Parameter(object):
164
170
  self._default = default
165
171
  self._value = default
166
172
  self._accepted_values = values
173
+ self._accepted_values_fn = valuesfn
167
174
  self._required = required
168
175
  self._const = const
169
176
  self._influence = influence
@@ -190,7 +197,7 @@ class Parameter(object):
190
197
  def highlight(value):
191
198
  return colors.bright(value) if self._is_default(value) else colors.dim(value)
192
199
 
193
- return "[{}]".format(", ".join([highlight(value) for value in accepted])) if accepted else ""
200
+ return "[{}]".format(", ".join([highlight(str(value)) for value in accepted])) if accepted else ""
194
201
 
195
202
  def __str__(self):
196
203
  """ Returns the parameter value as a string """
@@ -199,6 +206,8 @@ class Parameter(object):
199
206
  def _validate(self, value, what=None):
200
207
  if self._accepted_values is not None and value not in self._accepted_values:
201
208
  raise ParameterValueError(self, value, what=what)
209
+ if self._accepted_values_fn is not None and not self._accepted_values_fn(value):
210
+ raise ParameterValueError(self, value, what=what)
202
211
 
203
212
  def get_default(self):
204
213
  """ Get the default value of the parameter.
@@ -436,7 +445,7 @@ class IntParameter(Parameter):
436
445
  """
437
446
 
438
447
  def __init__(self, default=None, min=None, max=None, values=None, required=True, const=False,
439
- influence=True, help=None):
448
+ influence=True, help=None, valuesfn=None):
440
449
  """
441
450
  Creates a new parameter.
442
451
 
@@ -484,7 +493,8 @@ class IntParameter(Parameter):
484
493
  required=required,
485
494
  const=const,
486
495
  influence=influence,
487
- help=help)
496
+ help=help,
497
+ valuesfn=valuesfn)
488
498
 
489
499
  def _validate(self, value, what=None):
490
500
  if self._min is not None and value < self._min:
@@ -688,6 +698,10 @@ class ListParameter(Parameter):
688
698
  for item in value:
689
699
  if item not in self._accepted_values:
690
700
  raise ParameterValueError(self, item, what=what)
701
+ if self._accepted_values_fn is not None:
702
+ for item in value:
703
+ if not self._accepted_values_fn(item):
704
+ raise ParameterValueError(self, item, what=what)
691
705
 
692
706
  def get_value(self):
693
707
  return "+".join(self._value)
@@ -769,6 +783,7 @@ class TaskRegistry(object):
769
783
  self.env = env
770
784
  self.tasks = {}
771
785
  self.instances = {}
786
+ self._workspace_resources = []
772
787
 
773
788
  @staticmethod
774
789
  def get(*args, **kwargs):
@@ -777,6 +792,21 @@ class TaskRegistry(object):
777
792
  return TaskRegistry._instance
778
793
 
779
794
  def add_task_class(self, cls):
795
+ """
796
+ Add a task class to the registry.
797
+
798
+ The class is decorated to require workspace resources.
799
+ """
800
+
801
+ registry = self
802
+
803
+ def _workspace_resources(self):
804
+ return registry._workspace_resources
805
+
806
+ if not issubclass(cls, WorkspaceResource):
807
+ cls = attributes.requires("_workspace_resources")(cls)
808
+ cls._workspace_resources = property(_workspace_resources)
809
+
780
810
  self.tasks[cls.name] = cls
781
811
 
782
812
  def add_task(self, task, extra_params):
@@ -785,6 +815,13 @@ class TaskRegistry(object):
785
815
  full_name = utils.format_task_name(name, params)
786
816
  self.instances[full_name] = task
787
817
 
818
+ def require_workspace_resource(self, taskname):
819
+ name, _ = utils.parse_task_name(taskname)
820
+ task = self.get_task_class(name)
821
+ raise_task_error_if(task is None, name, "Resource not found")
822
+ raise_task_error_if(not issubclass(task, WorkspaceResource), name, "Not a workspace resource")
823
+ self._workspace_resources.append(taskname)
824
+
788
825
  def get_task_class(self, name):
789
826
  return self.tasks.get(name)
790
827
 
@@ -811,6 +848,10 @@ class TaskRegistry(object):
811
848
 
812
849
  raise_task_error_if(not task, full_name, "No such task")
813
850
 
851
+ def has_task(self, name):
852
+ name, params = utils.parse_task_name(name)
853
+ return self.tasks.get(name) is not None
854
+
814
855
  def set_default_parameters(self, task):
815
856
  name, params = utils.parse_task_name(task)
816
857
 
@@ -882,6 +923,14 @@ class TaskGenerator(object):
882
923
 
883
924
 
884
925
  class attributes:
926
+ @staticmethod
927
+ def arch(cls):
928
+ """ Return the architecture name (x86_64, arm64, etc.). """
929
+
930
+ cls._arch = Export(lambda t: platform.machine().lower().replace("amd64", "x86_64"))
931
+ cls.arch = property(lambda t: t._arch.value)
932
+ return cls
933
+
885
934
  @staticmethod
886
935
  def artifact(name, session=False):
887
936
  """Decorator adding an additional artifact to a task.
@@ -1167,6 +1216,65 @@ class attributes:
1167
1216
  return utils.concat_attributes("_publish_files", attrib)(cls)
1168
1217
  return decorate
1169
1218
 
1219
+ @staticmethod
1220
+ def common_metadata(aclocal=True, cmake=True, cxxinfo=True, path=True, pkgconfig=True):
1221
+ """
1222
+ Decorator adding common metadata to published artifacts.
1223
+
1224
+ The decorator adds common environment variables and C/C++ build information
1225
+ to the published artifact:
1226
+
1227
+ - Adds `bin/` to `PATH` environment variable if it exists.
1228
+ - Adds `lib/` and `lib64/` to C++ library paths if they exist.
1229
+ - Adds `include/` to C++ include paths if it exists.
1230
+ - Adds `lib/pkgconfig/`, `lib64/pkgconfig/` and `share/pkgconfig/` to
1231
+ `PKG_CONFIG_PATH` environment variable if `.pc` files are found.
1232
+ The `prefix` variable in `.pc` files is relocated to allow
1233
+ installation in arbitrary locations.
1234
+ - Adds `lib/cmake/`, `lib64/cmake/` and `share/cmake/` to
1235
+ `CMAKE_PREFIX_PATH` environment variable if they exist.
1236
+ - Adds `share/aclocal/` to `ACLOCAL_PATH` environment variable if it exists.
1237
+
1238
+ """
1239
+ def decorate(cls):
1240
+ _old_publish = cls.publish
1241
+
1242
+ @functools.wraps(cls.publish)
1243
+ def publish(self, artifact, tools):
1244
+ _old_publish(self, artifact, tools)
1245
+
1246
+ with tools.cwd(artifact.path):
1247
+ if path and tools.exists("bin"):
1248
+ artifact.environ.PATH.append("bin")
1249
+
1250
+ pcdirs = set()
1251
+
1252
+ for pcpath in tools.glob("lib/pkgconfig/*.pc") + tools.glob("lib64/pkgconfig/*.pc") + tools.glob("share/pkgconfig/*.pc"):
1253
+ pcdirs.add(fs.path.dirname(pcpath))
1254
+ tools.replace_in_file(pcpath, artifact.strings.install_prefix, "${{pcfiledir}}/../..")
1255
+ for pcpath in tools.glob("lib/*/pkgconfig/*.pc") + tools.glob("lib64/*/pkgconfig/*.pc"):
1256
+ pcdirs.add(fs.path.dirname(pcpath))
1257
+ tools.replace_in_file(pcpath, artifact.strings.install_prefix, "${{pcfiledir}}/../../..")
1258
+
1259
+ for pcdir in pcdirs:
1260
+ artifact.environ.PKG_CONFIG_PATH.append(pcdir)
1261
+
1262
+ if cmake and tools.exists("lib/cmake"):
1263
+ artifact.environ.CMAKE_PREFIX_PATH.append(".")
1264
+ if cmake and tools.exists("lib64/cmake"):
1265
+ artifact.environ.CMAKE_PREFIX_PATH.append(".")
1266
+ if cmake and tools.exists("share/cmake"):
1267
+ artifact.environ.CMAKE_PREFIX_PATH.append(".")
1268
+
1269
+ if aclocal and tools.exists("share/aclocal"):
1270
+ artifact.environ.ACLOCAL_PATH.append("share/aclocal")
1271
+
1272
+ cls.publish = publish
1273
+
1274
+ return cls
1275
+
1276
+ return decorate
1277
+
1170
1278
  @staticmethod
1171
1279
  def requires(attrib):
1172
1280
  """
@@ -2648,7 +2756,7 @@ class ReportProxy(object):
2648
2756
  if not filterfn(error):
2649
2757
  continue
2650
2758
  if error["location"] not in errors_by_location:
2651
- errors_by_location[error["location"]] = (error, [error["message"]], error["details"])
2759
+ errors_by_location[error["location"]] = (error, [error["message"]], error.get("details", ""))
2652
2760
  else:
2653
2761
  errors_by_location[error["location"]][1].append(error["message"])
2654
2762
 
@@ -2935,6 +3043,7 @@ class Download(Task):
2935
3043
  Once downloaded, archives are extracted and all of their files are published.
2936
3044
  If the file is not an archive it is published as is. Recognized archive extensions are:
2937
3045
 
3046
+ - .7z
2938
3047
  - .tar
2939
3048
  - .tar.bz2
2940
3049
  - .tar.gz
@@ -2997,7 +3106,7 @@ class Download(Task):
2997
3106
  return fs.posixpath.basename(url.path) or "file"
2998
3107
 
2999
3108
  def run(self, deps, tools):
3000
- supported_formats = [".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
3109
+ supported_formats = [".7z", ".tar", ".tar.bz2", ".tar.gz", ".tar.xz", ".tgz", ".zip"]
3001
3110
 
3002
3111
  raise_task_error_if(not self.url, self, "No URL(s) specified")
3003
3112
 
@@ -3094,7 +3203,8 @@ class Script(Task):
3094
3203
  doc = self.__doc__.split("---", 1)
3095
3204
  script = doc[1] if len(doc) > 1 else doc[0]
3096
3205
  script = script.splitlines()
3097
- script = [line[4:] for line in script]
3206
+ if os_sys.version_info < (3, 13):
3207
+ script = [line[4:] for line in script]
3098
3208
  script = "\n".join(script)
3099
3209
  script = script.lstrip()
3100
3210
  if not script.startswith("#!"):
@@ -3394,3 +3504,45 @@ class Test(Task):
3394
3504
  "{} tests out of {} were successful".format(
3395
3505
  len(self.testresult.successes),
3396
3506
  self.testresult.testsRun))
3507
+
3508
+
3509
+ @ArtifactAttributeSetProvider.Register
3510
+ class WorkspaceResourceAttributeSetProvider(ArtifactAttributeSetProvider):
3511
+ def create(self, artifact):
3512
+ pass
3513
+
3514
+ def parse(self, artifact, content):
3515
+ pass
3516
+
3517
+ def format(self, artifact, content):
3518
+ pass
3519
+
3520
+ def apply(self, task, artifact):
3521
+ resource = artifact.task
3522
+ node = artifact.get_node()
3523
+ if not node.is_workspace_resource():
3524
+ return
3525
+
3526
+ resource.deps = node.cache.get_context(node)
3527
+ resource.deps.__enter__()
3528
+
3529
+ try:
3530
+ resource.acquire(artifact=artifact, deps=resource.deps, tools=resource.tools, owner=task)
3531
+ except (KeyboardInterrupt, Exception) as e:
3532
+ if resource.release_on_error:
3533
+ with utils.ignore_exception():
3534
+ self.unapply(task, artifact)
3535
+ raise e
3536
+
3537
+ def unapply(self, task, artifact):
3538
+ resource = artifact.task
3539
+ node = artifact.get_node()
3540
+ if not node.is_workspace_resource():
3541
+ return
3542
+
3543
+ try:
3544
+ resource.release(artifact=artifact, deps=resource.deps, tools=resource.tools, owner=task)
3545
+ except Exception as e:
3546
+ raise e
3547
+
3548
+ resource.deps.__exit__(None, None, None)
jolt/tools.py CHANGED
@@ -1,3 +1,4 @@
1
+ import py7zr
1
2
  import bz2
2
3
  import copy
3
4
  import getpass
@@ -42,6 +43,9 @@ from jolt.error import raise_error_if
42
43
  from jolt.error import raise_task_error, raise_task_error_if
43
44
 
44
45
 
46
+ SUPPORTED_ARCHIVE_TYPES = [".tar", ".tar.bz2", ".tar.gz", ".tgz", ".tar.xz", ".tar.zst", ".zip"]
47
+
48
+
45
49
  http_session = Session()
46
50
 
47
51
 
@@ -83,11 +87,13 @@ class Reader(threading.Thread):
83
87
  self.logbuf.append((self, line))
84
88
 
85
89
 
86
- def _run(cmd, cwd, env, preexec_fn, *args, **kwargs):
90
+ def _run(cmd, cwd, env, *args, **kwargs):
87
91
  output = kwargs.get("output")
88
92
  output_on_error = kwargs.get("output_on_error")
89
93
  output_rstrip = kwargs.get("output_rstrip", True)
90
94
  output_stdio = kwargs.get("output_stdio", False)
95
+ output_stderr = kwargs.get("output_stderr", True)
96
+ output_stdout = kwargs.get("output_stdout", True)
91
97
  return_stderr = kwargs.get("return_stderr", False)
92
98
  output = output if output is not None else True
93
99
  output = False if output_on_error else output
@@ -130,11 +136,17 @@ def _run(cmd, cwd, env, preexec_fn, *args, **kwargs):
130
136
  shell=shell,
131
137
  cwd=cwd,
132
138
  env=env,
133
- preexec_fn=preexec_fn,
134
139
  )
135
140
 
136
- stdout_func = log.stdout if not output_stdio else stdout_write
137
- stderr_func = log.stderr if not output_stdio else stderr_write
141
+ if output_stdout:
142
+ stdout_func = log.stdout if not output_stdio else stdout_write
143
+ else:
144
+ stdout_func = None
145
+
146
+ if output_stderr:
147
+ stderr_func = log.stderr if not output_stdio else stderr_write
148
+ else:
149
+ stderr_func = None
138
150
 
139
151
  logbuf = []
140
152
  stdout = Reader(
@@ -160,7 +172,7 @@ def _run(cmd, cwd, env, preexec_fn, *args, **kwargs):
160
172
  p.wait(10)
161
173
  except subprocess.TimeoutExpired:
162
174
  kill(p.pid)
163
- p.wait()
175
+ utils.call_and_catch(p.wait, 10)
164
176
  raise
165
177
 
166
178
  except (subprocess.TimeoutExpired, JoltTimeoutError):
@@ -170,7 +182,7 @@ def _run(cmd, cwd, env, preexec_fn, *args, **kwargs):
170
182
  p.wait(10)
171
183
  except subprocess.TimeoutExpired:
172
184
  kill(p.pid)
173
- p.wait()
185
+ utils.call_and_catch(p.wait, 10)
174
186
 
175
187
  finally:
176
188
  if stdout:
@@ -249,6 +261,10 @@ class _CMake(object):
249
261
  self.builddir = self.tools.builddir(incremental=incremental)
250
262
  self.installdir = self.tools.builddir("install", incremental=False)
251
263
 
264
+ def clean(self):
265
+ self.tools.rmtree(self.builddir, ignore_errors=True)
266
+ self.tools.rmtree(self.installdir, ignore_errors=True)
267
+
252
268
  def configure(self, sourcedir, *args, generator=None, **kwargs):
253
269
  sourcedir = self.tools.expand_path(sourcedir)
254
270
 
@@ -259,74 +275,85 @@ class _CMake(object):
259
275
 
260
276
  with self.tools.cwd(self.builddir):
261
277
  self.tools.run(
262
- "cmake {0} -B{1} -DCMAKE_INSTALL_PREFIX={2} {3} {4}",
278
+ "cmake {0} {1} -DCMAKE_INSTALL_PREFIX=/jolt-prefix {1} {2} {3}",
263
279
  sourcedir,
264
- self.builddir,
265
- self.installdir,
280
+ utils.option("-B", self.builddir),
266
281
  utils.option("-G", generator),
267
282
  extra_args,
268
283
  output=True)
269
284
 
270
- def build(self, release=True, *args, **kwargs):
285
+ def build(self, *args, config="Release", **kwargs):
271
286
  threading_args = ' -j {}'.format(kwargs.get("threads", self.tools.thread_count()))
272
287
  with self.tools.cwd(self.builddir):
273
- release = "--config Release" if release else ""
274
- self.tools.run("cmake --build . {0}{1}", release, threading_args, output=True)
288
+ self.tools.run("cmake --build . --config {0} {1}", config, threading_args, output=True)
275
289
 
276
- def install(self, release=True, *args, **kwargs):
277
- with self.tools.cwd(self.builddir):
278
- release = "--config Release" if release else ""
279
- self.tools.run("cmake --build . --target install {0}", release, output=True)
290
+ def install(self, target="install", config="Release", **kwargs):
291
+ with self.tools.cwd(self.builddir), self.tools.environ(DESTDIR=self.installdir):
292
+ self.tools.run("cmake --build . --config {0} --target {1}", config, target, output=True)
280
293
 
281
- def publish(self, artifact, files='*', *args, **kwargs):
282
- with self.tools.cwd(self.installdir):
283
- artifact.collect(files, *args, **kwargs)
294
+ def publish(self, artifact, files='*', symlinks=True, *args, **kwargs):
295
+ with self.tools.cwd(self.installdir, "jolt-prefix"):
296
+ artifact.collect(files, *args, symlinks=symlinks, **kwargs)
297
+ artifact.strings.install_prefix = "/jolt-prefix"
284
298
 
285
299
 
286
300
  class _Meson(object):
287
- def __init__(self, deps, tools):
301
+ def __init__(self, deps, tools, incremental=False):
288
302
  self.deps = deps
289
303
  self.tools = tools
290
- self.builddir = self.tools.builddir()
291
- self.installdir = self.tools.builddir("install")
304
+ self.builddir = self.tools.builddir(incremental=incremental)
305
+ self.installdir = self.tools.builddir("install", incremental=False)
306
+ self.prefix = "/jolt-prefix" if os.name != "nt" else "C:\\jolt-prefix"
307
+
308
+ def clean(self):
309
+ self.tools.rmtree(self.builddir, ignore_errors=True)
310
+ self.tools.rmtree(self.installdir, ignore_errors=True)
292
311
 
293
312
  def configure(self, sourcedir, *args, **kwargs):
294
313
  sourcedir = self.tools.expand_path(sourcedir)
295
- self.tools.run("meson --prefix=/ {0} {1}", sourcedir, self.builddir,
314
+ options = " ".join([f"-D{arg}" for arg in args]) + " "
315
+ options += " ".join(["-D{0}={1}".format(key, self.tools.expand(val)) for key, val in kwargs.items()])
316
+ self.tools.run("meson setup --prefix={0} {1} {2} {3}", self.prefix, sourcedir, self.builddir, options,
296
317
  output=True)
297
318
 
298
319
  def build(self, *args, **kwargs):
299
320
  self.tools.run("ninja -C {0} ", self.builddir, output=True)
300
321
 
301
322
  def install(self, *args, **kwargs):
302
- self.tools.run("DESTDIR={0} ninja -C {1} install",
303
- self.installdir, self.builddir,
304
- output=True)
323
+ with self.tools.environ(DESTDIR=self.installdir):
324
+ self.tools.run("ninja -C {0} install", self.builddir, output=True)
305
325
 
306
- def publish(self, artifact, files='*', *args, **kwargs):
307
- with self.tools.cwd(self.installdir):
308
- artifact.collect(files, *args, **kwargs)
326
+ def publish(self, artifact, files='*', symlinks=True, *args, **kwargs):
327
+ with self.tools.cwd(self.installdir, "jolt-prefix"):
328
+ artifact.collect(files, *args, symlinks=symlinks, **kwargs)
329
+ artifact.strings.install_prefix = self.prefix
309
330
 
310
331
 
311
332
  class _AutoTools(object):
312
- def __init__(self, deps, tools):
333
+ def __init__(self, deps, tools, incremental=False):
313
334
  self.deps = deps
314
335
  self.tools = tools
315
- self.builddir = self.tools.builddir()
316
- self.installdir = self.tools.builddir("install")
336
+ self.builddir = self.tools.builddir(incremental=incremental)
337
+ self.installdir = self.tools.builddir("install", incremental=False)
338
+ self.prefix = "jolt-prefix"
317
339
 
318
- def configure(self, sourcedir, *args, **kwargs):
340
+ def clean(self):
341
+ self.tools.rmtree(self.builddir, ignore_errors=True)
342
+ self.tools.rmtree(self.installdir, ignore_errors=True)
343
+
344
+ def configure(self, sourcedir, *args):
319
345
  sourcedir = self.tools.expand_path(sourcedir)
320
- prefix = kwargs.get("prefix", "/")
321
346
 
322
347
  if not fs.path.exists(fs.path.join(sourcedir, "configure")):
323
348
  with self.tools.cwd(sourcedir):
324
349
  self.tools.run("autoreconf -visf", output=True)
325
350
 
326
351
  with self.tools.cwd(self.builddir), self.tools.environ(DESTDIR=self.installdir):
327
- self.tools.run("{0}/configure --prefix={1} {2}",
328
- sourcedir, prefix,
352
+ self.tools.run("{0}/configure --prefix=/{1} {2} {3}",
353
+ sourcedir,
354
+ self.prefix,
329
355
  self.tools.getenv("CONFIGURE_FLAGS", ""),
356
+ " ".join(args),
330
357
  output=True)
331
358
 
332
359
  def build(self, *args, **kwargs):
@@ -334,13 +361,14 @@ class _AutoTools(object):
334
361
  self.tools.run("make VERBOSE=yes Q= V=1 -j{0}",
335
362
  self.tools.cpu_count(), output=True)
336
363
 
337
- def install(self, target="install", **kwargs):
338
- with self.tools.cwd(self.builddir), self.tools.environ(DESTDIR=self.installdir):
339
- self.tools.run("make {}", target, output=True)
364
+ def install(self, target="install"):
365
+ with self.tools.cwd(self.builddir):
366
+ self.tools.run("make DESTDIR={} {}", self.installdir, target, output=True)
340
367
 
341
- def publish(self, artifact, files='*', *args, **kwargs):
342
- with self.tools.cwd(self.installdir):
343
- artifact.collect(files, *args, **kwargs)
368
+ def publish(self, artifact, files='*', symlinks=True, *args, **kwargs):
369
+ with self.tools.cwd(self.installdir, self.prefix):
370
+ artifact.collect(files, *args, symlinks=symlinks, **kwargs)
371
+ artifact.strings.install_prefix = "/" + self.prefix
344
372
 
345
373
 
346
374
  class ZipFile(zipfile.ZipFile):
@@ -471,7 +499,6 @@ class Tools(object):
471
499
  self._chroot_path = []
472
500
  self._deadline = None
473
501
  self._run_prefix = []
474
- self._preexec_fn = None
475
502
  self._cwd = fs.path.normpath(fs.path.join(config.get_workdir(), cwd or config.get_workdir()))
476
503
  self._env = copy.deepcopy(env or os.environ)
477
504
  self._task = task
@@ -520,6 +547,12 @@ class Tools(object):
520
547
  zf.write(path, zippath)
521
548
  return filename
522
549
 
550
+ def _make_7zfile(self, filename, fmt, rootdir):
551
+ self.mkdirname(filename)
552
+ with py7zr.SevenZipFile(filename, 'w') as archive:
553
+ archive.writeall(rootdir, ".")
554
+ return filename
555
+
523
556
  def _make_tarfile(self, filename, fmt, rootdir):
524
557
  self.mkdirname(filename)
525
558
  with tarfile.open(filename, 'w|%s' % fmt) as tar:
@@ -552,6 +585,7 @@ class Tools(object):
552
585
  The type of archive to create is determined by the filename extension.
553
586
  Supported formats are:
554
587
 
588
+ - 7z
555
589
  - tar
556
590
  - tar.bz2
557
591
  - tar.gz
@@ -587,12 +621,16 @@ class Tools(object):
587
621
  fmt = "tarbz2"
588
622
  elif filename.endswith(".tar.xz"):
589
623
  fmt = "tarxz"
624
+ elif filename.endswith(".7z"):
625
+ fmt = "7z"
590
626
  raise_task_error_if(
591
627
  not fmt, self._task,
592
628
  "unknown archive type '{0}'", fs.path.basename(filename))
593
629
  try:
594
630
  if fmt == "zip":
595
631
  outfile = self._make_zipfile(filename, fmt, rootdir=pathname)
632
+ elif fmt == "7z":
633
+ outfile = self._make_7zfile(filename, fmt, rootdir=pathname)
596
634
  else:
597
635
  outfile = self._make_tarfile(filename, fmt[3:], rootdir=pathname)
598
636
  if outfile != filename:
@@ -601,9 +639,9 @@ class Tools(object):
601
639
  except Exception:
602
640
  raise_task_error(self._task, "failed to create archive from directory '{0}'", pathname)
603
641
 
604
- def autotools(self, deps=None):
642
+ def autotools(self, deps=None, incremental=False):
605
643
  """ Creates an AutoTools invokation helper """
606
- return _AutoTools(deps, self)
644
+ return _AutoTools(deps, self, incremental=incremental)
607
645
 
608
646
  @utils.locked(lock='_builddir_lock')
609
647
  def builddir(self, name=None, incremental=False, unique=True):
@@ -888,7 +926,7 @@ class Tools(object):
888
926
  pbar.update(len(data))
889
927
  actual_size = self.file_size(pathname)
890
928
  raise_error_if(
891
- size != 0 and size != actual_size,
929
+ size != 0 and size > actual_size,
892
930
  f"Downloaded file was truncated to {actual_size}/{size} bytes: {name}")
893
931
 
894
932
  return response.status_code == 200
@@ -1047,6 +1085,7 @@ class Tools(object):
1047
1085
 
1048
1086
  Supported formats are:
1049
1087
 
1088
+ - 7z
1050
1089
  - tar
1051
1090
  - tar.bz2
1052
1091
  - tar.gz
@@ -1110,6 +1149,13 @@ class Tools(object):
1110
1149
  self._extract_tarzstd(filename, filepath, files)
1111
1150
  except tarfile.StreamError as e:
1112
1151
  raise_task_error(self._task, "failed to extract archive '{0}': {1}", filename, str(e))
1152
+ elif filename.endswith(".7z"):
1153
+ with py7zr.SevenZipFile(filename, 'r') as archive:
1154
+ if files:
1155
+ for file in files:
1156
+ archive.extract(file, filepath)
1157
+ else:
1158
+ archive.extractall(filepath)
1113
1159
  else:
1114
1160
  raise_task_error(self._task, "unknown archive type '{0}'", fs.path.basename(filename))
1115
1161
  except Exception:
@@ -1261,9 +1307,9 @@ class Tools(object):
1261
1307
  """
1262
1308
  return utils.map_concurrent(callable, iterable, max_workers)
1263
1309
 
1264
- def meson(self, deps=None):
1310
+ def meson(self, deps=None, incremental=False):
1265
1311
  """ Creates a Meson invokation helper """
1266
- return _Meson(deps, self)
1312
+ return _Meson(deps, self, incremental=incremental)
1267
1313
 
1268
1314
  @contextmanager
1269
1315
  def nixpkgs(self, nixfile=None, packages=None, pure=False, path=None, options=None):
@@ -1539,6 +1585,9 @@ class Tools(object):
1539
1585
  refuses to terminate, it will be killed after an additional
1540
1586
  10 seconds have passed. Default: None.
1541
1587
 
1588
+ Returns:
1589
+ str: stdout from command unless output=False
1590
+
1542
1591
  Example:
1543
1592
 
1544
1593
  .. code-block:: python
@@ -1578,7 +1627,7 @@ class Tools(object):
1578
1627
  except Exception:
1579
1628
  pass
1580
1629
 
1581
- return _run(cmd, self._cwd, self._env, self._preexec_fn, *args, **kwargs)
1630
+ return _run(cmd, self._cwd, self._env, *args, **kwargs)
1582
1631
 
1583
1632
  finally:
1584
1633
  if stdi:
@@ -1692,11 +1741,15 @@ class Tools(object):
1692
1741
  fs.makedirs(path)
1693
1742
  for relsrcpath, reldstpath in artifact.files.items():
1694
1743
  srcpath = fs.path.normpath(fs.path.join(artifact.task.joltdir, relsrcpath))
1744
+ srcpath = self.expand_path(srcpath)
1695
1745
  dstpath = fs.path.normpath(fs.path.join(path, reldstpath))
1746
+ dstpath = self.expand_path(dstpath)
1747
+
1696
1748
  if dstpath != fs.path.realpath(dstpath):
1697
1749
  log.debug("Cannot symlink '{} -> {}', parent directory already symlinked",
1698
1750
  srcpath, dstpath)
1699
1751
  continue
1752
+
1700
1753
  if fs.path.isdir(dstpath):
1701
1754
  files = fs.scandir(srcpath)
1702
1755
  for file in files:
@@ -1706,7 +1759,8 @@ class Tools(object):
1706
1759
  self.symlink(srcpath, dstpath)
1707
1760
 
1708
1761
  # Restore missing srcfiles if they resided in a build directory
1709
- if srcpath.startswith(artifact.tools.buildroot) and \
1762
+ buildroot_abs = self.expand_path(artifact.tools.buildroot)
1763
+ if srcpath.startswith(buildroot_abs) and \
1710
1764
  not fs.path.exists(srcpath):
1711
1765
  fs.copy(fs.path.join(artifact.path, reldstpath), srcpath, symlinks=True)
1712
1766
  self.write_file(meta, artifact.path)
@@ -1865,7 +1919,7 @@ class Tools(object):
1865
1919
 
1866
1920
  if type(chroot) in [cache.Artifact, cache.ArtifactToolsProxy]:
1867
1921
  raise_task_error_if(
1868
- not str(chroot.paths.rootfs), self._task,
1922
+ not chroot.paths.rootfs, self._task,
1869
1923
  "No 'rootfs' path in artifact")
1870
1924
  chroot = chroot.paths.rootfs
1871
1925