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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. jolt/__init__.py +88 -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 +839 -367
  6. jolt/chroot.py +156 -0
  7. jolt/cli.py +362 -143
  8. jolt/common_pb2.py +63 -0
  9. jolt/common_pb2_grpc.py +4 -0
  10. jolt/config.py +99 -42
  11. jolt/error.py +19 -4
  12. jolt/expires.py +2 -2
  13. jolt/filesystem.py +8 -6
  14. jolt/graph.py +705 -117
  15. jolt/hooks.py +63 -1
  16. jolt/influence.py +129 -6
  17. jolt/loader.py +369 -121
  18. jolt/log.py +225 -63
  19. jolt/manifest.py +28 -38
  20. jolt/options.py +35 -10
  21. jolt/pkgs/abseil.py +42 -0
  22. jolt/pkgs/asio.py +25 -0
  23. jolt/pkgs/autoconf.py +41 -0
  24. jolt/pkgs/automake.py +41 -0
  25. jolt/pkgs/b2.py +31 -0
  26. jolt/pkgs/boost.py +111 -0
  27. jolt/pkgs/boringssl.py +32 -0
  28. jolt/pkgs/busybox.py +39 -0
  29. jolt/pkgs/bzip2.py +43 -0
  30. jolt/pkgs/cares.py +29 -0
  31. jolt/pkgs/catch2.py +36 -0
  32. jolt/pkgs/cbindgen.py +17 -0
  33. jolt/pkgs/cista.py +19 -0
  34. jolt/pkgs/clang.py +44 -0
  35. jolt/pkgs/cli11.py +23 -0
  36. jolt/pkgs/cmake.py +48 -0
  37. jolt/pkgs/cpython.py +196 -0
  38. jolt/pkgs/crun.py +29 -0
  39. jolt/pkgs/curl.py +38 -0
  40. jolt/pkgs/dbus.py +18 -0
  41. jolt/pkgs/double_conversion.py +24 -0
  42. jolt/pkgs/fastfloat.py +21 -0
  43. jolt/pkgs/ffmpeg.py +28 -0
  44. jolt/pkgs/flatbuffers.py +29 -0
  45. jolt/pkgs/fmt.py +27 -0
  46. jolt/pkgs/fstree.py +20 -0
  47. jolt/pkgs/gflags.py +18 -0
  48. jolt/pkgs/glib.py +18 -0
  49. jolt/pkgs/glog.py +25 -0
  50. jolt/pkgs/glslang.py +21 -0
  51. jolt/pkgs/golang.py +16 -11
  52. jolt/pkgs/googlebenchmark.py +18 -0
  53. jolt/pkgs/googletest.py +46 -0
  54. jolt/pkgs/gperf.py +15 -0
  55. jolt/pkgs/grpc.py +73 -0
  56. jolt/pkgs/hdf5.py +19 -0
  57. jolt/pkgs/help2man.py +14 -0
  58. jolt/pkgs/inja.py +28 -0
  59. jolt/pkgs/jsoncpp.py +31 -0
  60. jolt/pkgs/libarchive.py +43 -0
  61. jolt/pkgs/libcap.py +44 -0
  62. jolt/pkgs/libdrm.py +44 -0
  63. jolt/pkgs/libedit.py +42 -0
  64. jolt/pkgs/libevent.py +31 -0
  65. jolt/pkgs/libexpat.py +27 -0
  66. jolt/pkgs/libfastjson.py +21 -0
  67. jolt/pkgs/libffi.py +16 -0
  68. jolt/pkgs/libglvnd.py +30 -0
  69. jolt/pkgs/libogg.py +28 -0
  70. jolt/pkgs/libpciaccess.py +18 -0
  71. jolt/pkgs/libseccomp.py +21 -0
  72. jolt/pkgs/libtirpc.py +24 -0
  73. jolt/pkgs/libtool.py +42 -0
  74. jolt/pkgs/libunwind.py +35 -0
  75. jolt/pkgs/libva.py +18 -0
  76. jolt/pkgs/libvorbis.py +33 -0
  77. jolt/pkgs/libxml2.py +35 -0
  78. jolt/pkgs/libxslt.py +17 -0
  79. jolt/pkgs/libyajl.py +16 -0
  80. jolt/pkgs/llvm.py +81 -0
  81. jolt/pkgs/lua.py +54 -0
  82. jolt/pkgs/lz4.py +26 -0
  83. jolt/pkgs/m4.py +14 -0
  84. jolt/pkgs/make.py +17 -0
  85. jolt/pkgs/mesa.py +81 -0
  86. jolt/pkgs/meson.py +17 -0
  87. jolt/pkgs/mstch.py +28 -0
  88. jolt/pkgs/mysql.py +60 -0
  89. jolt/pkgs/nasm.py +49 -0
  90. jolt/pkgs/ncurses.py +30 -0
  91. jolt/pkgs/ng_log.py +25 -0
  92. jolt/pkgs/ninja.py +45 -0
  93. jolt/pkgs/nlohmann_json.py +25 -0
  94. jolt/pkgs/nodejs.py +19 -11
  95. jolt/pkgs/opencv.py +24 -0
  96. jolt/pkgs/openjdk.py +26 -0
  97. jolt/pkgs/openssl.py +103 -0
  98. jolt/pkgs/paho.py +76 -0
  99. jolt/pkgs/patchelf.py +16 -0
  100. jolt/pkgs/perl.py +42 -0
  101. jolt/pkgs/pkgconfig.py +64 -0
  102. jolt/pkgs/poco.py +39 -0
  103. jolt/pkgs/protobuf.py +77 -0
  104. jolt/pkgs/pugixml.py +27 -0
  105. jolt/pkgs/python.py +19 -0
  106. jolt/pkgs/qt.py +35 -0
  107. jolt/pkgs/rapidjson.py +26 -0
  108. jolt/pkgs/rapidyaml.py +28 -0
  109. jolt/pkgs/re2.py +30 -0
  110. jolt/pkgs/re2c.py +17 -0
  111. jolt/pkgs/readline.py +15 -0
  112. jolt/pkgs/rust.py +41 -0
  113. jolt/pkgs/sdl.py +28 -0
  114. jolt/pkgs/simdjson.py +27 -0
  115. jolt/pkgs/soci.py +46 -0
  116. jolt/pkgs/spdlog.py +29 -0
  117. jolt/pkgs/spirv_llvm.py +21 -0
  118. jolt/pkgs/spirv_tools.py +24 -0
  119. jolt/pkgs/sqlite.py +83 -0
  120. jolt/pkgs/ssl.py +12 -0
  121. jolt/pkgs/texinfo.py +15 -0
  122. jolt/pkgs/tomlplusplus.py +22 -0
  123. jolt/pkgs/wayland.py +26 -0
  124. jolt/pkgs/x11.py +58 -0
  125. jolt/pkgs/xerces_c.py +20 -0
  126. jolt/pkgs/xorg.py +360 -0
  127. jolt/pkgs/xz.py +29 -0
  128. jolt/pkgs/yamlcpp.py +30 -0
  129. jolt/pkgs/zeromq.py +47 -0
  130. jolt/pkgs/zlib.py +69 -0
  131. jolt/pkgs/zstd.py +33 -0
  132. jolt/plugins/alias.py +3 -0
  133. jolt/plugins/allure.py +5 -2
  134. jolt/plugins/autotools.py +66 -0
  135. jolt/plugins/cache.py +133 -0
  136. jolt/plugins/cmake.py +74 -6
  137. jolt/plugins/conan.py +238 -0
  138. jolt/plugins/cxx.py +698 -0
  139. jolt/plugins/cxxinfo.py +7 -0
  140. jolt/plugins/dashboard.py +1 -1
  141. jolt/plugins/docker.py +91 -23
  142. jolt/plugins/email.py +5 -2
  143. jolt/plugins/email.xslt +144 -101
  144. jolt/plugins/environ.py +11 -0
  145. jolt/plugins/fetch.py +141 -0
  146. jolt/plugins/gdb.py +44 -21
  147. jolt/plugins/gerrit.py +1 -14
  148. jolt/plugins/git.py +316 -101
  149. jolt/plugins/googletest.py +522 -1
  150. jolt/plugins/http.py +36 -38
  151. jolt/plugins/libtool.py +63 -0
  152. jolt/plugins/linux.py +990 -0
  153. jolt/plugins/logstash.py +4 -4
  154. jolt/plugins/meson.py +61 -0
  155. jolt/plugins/ninja-compdb.py +107 -31
  156. jolt/plugins/ninja.py +929 -134
  157. jolt/plugins/paths.py +11 -1
  158. jolt/plugins/pkgconfig.py +219 -0
  159. jolt/plugins/podman.py +148 -91
  160. jolt/plugins/python.py +137 -0
  161. jolt/plugins/remote_execution/__init__.py +0 -0
  162. jolt/plugins/remote_execution/administration_pb2.py +46 -0
  163. jolt/plugins/remote_execution/administration_pb2_grpc.py +170 -0
  164. jolt/plugins/remote_execution/log_pb2.py +32 -0
  165. jolt/plugins/remote_execution/log_pb2_grpc.py +68 -0
  166. jolt/plugins/remote_execution/scheduler_pb2.py +41 -0
  167. jolt/plugins/remote_execution/scheduler_pb2_grpc.py +141 -0
  168. jolt/plugins/remote_execution/worker_pb2.py +38 -0
  169. jolt/plugins/remote_execution/worker_pb2_grpc.py +112 -0
  170. jolt/plugins/report.py +12 -2
  171. jolt/plugins/rust.py +25 -0
  172. jolt/plugins/scheduler.py +710 -0
  173. jolt/plugins/selfdeploy/setup.py +9 -4
  174. jolt/plugins/selfdeploy.py +138 -88
  175. jolt/plugins/strings.py +35 -22
  176. jolt/plugins/symlinks.py +26 -11
  177. jolt/plugins/telemetry.py +5 -2
  178. jolt/plugins/timeline.py +13 -3
  179. jolt/plugins/volume.py +46 -48
  180. jolt/scheduler.py +591 -191
  181. jolt/tasks.py +1783 -245
  182. jolt/templates/export.sh.template +12 -6
  183. jolt/templates/timeline.html.template +44 -47
  184. jolt/timer.py +22 -0
  185. jolt/tools.py +749 -302
  186. jolt/utils.py +245 -18
  187. jolt/version.py +1 -1
  188. jolt/version_utils.py +2 -2
  189. jolt/xmldom.py +12 -2
  190. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/METADATA +98 -38
  191. jolt-0.9.429.dist-info/RECORD +207 -0
  192. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/WHEEL +1 -1
  193. jolt/plugins/amqp.py +0 -834
  194. jolt/plugins/debian.py +0 -338
  195. jolt/plugins/ftp.py +0 -181
  196. jolt/plugins/ninja-cache.py +0 -64
  197. jolt/plugins/ninjacli.py +0 -271
  198. jolt/plugins/repo.py +0 -253
  199. jolt-0.9.76.dist-info/RECORD +0 -79
  200. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/entry_points.txt +0 -0
  201. {jolt-0.9.76.dist-info → jolt-0.9.429.dist-info}/top_level.txt +0 -0
jolt/plugins/ninja.py CHANGED
@@ -1,6 +1,11 @@
1
+ from collections import OrderedDict
2
+ import contextlib
1
3
  import copy
2
- import ninja_syntax as ninja
4
+ import functools
5
+ from ninja import ninja_syntax as ninja
3
6
  import os
7
+ import platform
8
+ import re
4
9
  import sys
5
10
 
6
11
  from jolt.tasks import Task, attributes as task_attributes
@@ -8,16 +13,27 @@ from jolt import config
8
13
  from jolt.influence import attribute as influence_attribute
9
14
  from jolt.influence import DirectoryInfluence, FileInfluence
10
15
  from jolt.influence import HashInfluenceProvider, TaskAttributeInfluence
16
+ from jolt.config import get_cachedir
11
17
  from jolt import log
12
18
  from jolt import utils
13
19
  from jolt import filesystem as fs
20
+ from jolt.error import raise_task_error
14
21
  from jolt.error import raise_task_error_if
15
22
  from jolt.error import JoltError, JoltCommandError
16
23
 
17
24
 
25
+ c_standard_default = 17
26
+ cxx_standard_default = 17
27
+ c_standards_list = [90, 99, 11, 17, 23]
28
+ cxx_standards_list = [98, 11, 14, 17, 20, 23, 26]
29
+
30
+
18
31
  class CompileError(JoltError):
19
- def __init__(self):
20
- super().__init__("Compilation failed")
32
+ def __init__(self, error):
33
+ if error:
34
+ super().__init__(f"{error.type}: {error.location}: {error.message}")
35
+ else:
36
+ super().__init__("Compilation failed")
21
37
 
22
38
 
23
39
  class attributes:
@@ -34,6 +50,7 @@ class attributes:
34
50
  Keywords are expanded.
35
51
  prepend (boolean): Prepend the value of the alternative
36
52
  attribute. Default: false (append).
53
+
37
54
  """
38
55
  return utils.concat_attributes("asflags", attrib, prepend)
39
56
 
@@ -50,9 +67,438 @@ class attributes:
50
67
  Keywords are expanded.
51
68
  prepend (boolean): Prepend the value of the alternative
52
69
  attribute. Default: false (append).
70
+
53
71
  """
54
72
  return utils.concat_attributes("cflags", attrib, prepend)
55
73
 
74
+ @staticmethod
75
+ def coverage_data(publish=True):
76
+ """Task decorator collecting coverage data files (.gcda) from instrumented executables.
77
+
78
+ The decorator sets the ``GCOV_PREFIX`` environment variable
79
+ during execution of the task. Data files generated by
80
+ instrumented executable are then collected into a dedicated
81
+ build directory and published upon completion of the task.
82
+
83
+ The decorator also republishes coverage note files found in
84
+ dependency artifacts.
85
+
86
+ The task artifact is annotated with
87
+ ``artifact.paths.coverage_data`` which points out the path to
88
+ the data files within the artifact.
89
+
90
+ Args:
91
+ publish (boolean): Publish coverage data with artifact.
92
+ Default: True.
93
+
94
+ Example:
95
+
96
+ .. literalinclude:: ../examples/code_coverage/coverage.jolt
97
+ :language: python
98
+ :caption: examples/code_coverage/coverage.jolt
99
+
100
+ """
101
+
102
+ def decorate(cls):
103
+ class CoverageDataMixin(object):
104
+ @contextlib.contextmanager
105
+ def run_coverage_data(self, deps, tools):
106
+ self.covdatadir = tools.builddir("coverage-data")
107
+ self.info("Collecting coverage data into {covdatadir}")
108
+
109
+ for _, artifact in deps.items():
110
+ if artifact.paths.coverage_data:
111
+ tools.copy(str(artifact.paths.coverage_data), self.covdatadir)
112
+
113
+ with tools.environ(GCOV_PREFIX=self.covdatadir):
114
+ yield
115
+
116
+ @utils.cached.instance
117
+ def publish_coverage_data(self, artifact, tools):
118
+ if not publish:
119
+ return
120
+
121
+ self.verbose("Publishing coverage data from {covdatadir}")
122
+ with tools.cwd(self.covdatadir):
123
+ if artifact.collect("**/*.gc*", "cov/"):
124
+ artifact.paths.coverage_data = "cov"
125
+
126
+ class CoverageData(cls, CoverageDataMixin):
127
+ @functools.wraps(cls.run)
128
+ def run(self, deps, tools):
129
+ with self.run_coverage_data(deps, tools):
130
+ super().run(deps, tools)
131
+
132
+ def publish_coverage_data(self, artifact, tools):
133
+ CoverageDataMixin.publish_coverage_data(self, artifact, tools)
134
+
135
+ @functools.wraps(cls.publish)
136
+ def publish(self, artifact, tools):
137
+ super().publish(artifact, tools)
138
+ if publish:
139
+ self.publish_coverage_data(artifact, tools)
140
+
141
+ CoverageData.__doc__ = cls.__doc__
142
+ CoverageData.__name__ = cls.__name__
143
+ return CoverageData
144
+
145
+ return decorate
146
+
147
+ @staticmethod
148
+ def coverage_report_gcov(branches=True, demangle=True, coverage_data=False):
149
+ """Decorator generating gcov reports from collected coverage data.
150
+
151
+ A gcov report file is generated for each instrumented source
152
+ file in the executable. The reports consists of plain-text
153
+ source code annotated with code coverage details.
154
+
155
+ The decorator can be used standalone or in combination with
156
+ :func:`coverage_data`. In either case, coverage data is
157
+ collected from dependency artifacts and processed together
158
+ with any data generated by the task itself.
159
+
160
+ The following class attributes may be set to control the behavior
161
+ of lcov:
162
+
163
+ - ``gcov_branches`` - Enable branch coverage.
164
+ Passed as ``-b`` on the command line. Default: ``True``
165
+ - ``gcov_demangle`` - Display demangled function names in output.
166
+ Passed as ``-m`` on the command line. Default: ``True``
167
+
168
+ The task artifact is annotated with
169
+ ``artifact.paths.coverage_report_gcov`` which points out the
170
+ path to the gcov reports within the artifact.
171
+
172
+ A gcov executable must exist in PATH. Set the ``GCOV`` environment
173
+ variable to override its default name.
174
+
175
+ Args:
176
+ branches (boolean): Enable branch coverage. Default: True.
177
+ Overridden by class attribute ``gcov_branches``, if set.
178
+ demangle (boolean): Display demangled function names in output. Default: True.
179
+ Overridden by class attribute ``gcov_demangle``, if set.
180
+ coverage_data (boolean): Republish coverage data found in
181
+ dependency artifacts. Default: ``False``
182
+ Overridden by class attribute ``gcov_coverage_data``, if set.
183
+
184
+ Example:
185
+
186
+ .. literalinclude:: ../examples/code_coverage/coverage.jolt
187
+ :language: python
188
+ :caption: examples/code_coverage/coverage.jolt
189
+
190
+ """
191
+
192
+ def decorate(cls):
193
+ class CoverageReportGcovMixin(object):
194
+ def run_coverage_report_gcov(self, deps, tools):
195
+ self.info("Generating GCOV code coverage report")
196
+
197
+ gcov = tools.getenv("GCOV", tools.getenv("CROSS_COMPILE", "") + "gcov")
198
+ if not tools.which(gcov):
199
+ raise_task_error(self, "gcov not found in PATH, cannot generate coverage report.")
200
+
201
+ # Assume data is already copied by coverage_data() if the covdatadir exists,
202
+ # otherwise copy it from dependency artifacts.
203
+ if not hasattr(self, "covdatadir"):
204
+ self.covdatadir = tools.builddir("coverage-data")
205
+ for _, artifact in deps.items():
206
+ if artifact.paths.coverage_data:
207
+ tools.copy(str(artifact.paths.coverage_data), self.covdatadir)
208
+
209
+ with tools.cwd(self.covdatadir):
210
+ datafiles = tools.glob("**/*.gcda")
211
+
212
+ reportdir = tools.builddir("coverage-report-gcov")
213
+
214
+ with tools.cwd(tools.wsroot):
215
+ for file in datafiles:
216
+ infile = os.path.join(self.covdatadir, file)
217
+ branchflag = "-b " if bool(getattr(self, "gcov_branches", branches)) else ""
218
+ demangleflag = "-m " if bool(getattr(self, "gcov_demangle", demangle)) else ""
219
+ output = tools.run(
220
+ "{} {}{}-t -p -s {covdatadir} {}",
221
+ gcov, branchflag, demangleflag, infile, output_on_error=True)
222
+ output = output.replace(self.covdatadir, "")
223
+ source = re.search(r"0:Source:(.*)", output)
224
+ if not source:
225
+ self.warning(f"No source file found in gcov output for {file}")
226
+ continue
227
+ report = source.group(1) + ".gcov"
228
+ report_path = os.path.join(reportdir, report)
229
+ tools.mkdirname(report_path)
230
+ tools.write_file(report_path, output, expand=False)
231
+
232
+ @utils.cached.instance
233
+ def publish_coverage_data(self, artifact, tools):
234
+ if not bool(getattr(self, "gcov_coverage_data", coverage_data)):
235
+ return
236
+
237
+ self.verbose("Publishing coverage data from {covdatadir}")
238
+ with tools.cwd(self.covdatadir):
239
+ if artifact.collect("**/*.gc*", "cov/"):
240
+ artifact.paths.coverage_data = "cov"
241
+
242
+ def publish_coverage_report_gcov(self, artifact, tools):
243
+ with tools.cwd(tools.builddir("coverage-report-gcov")):
244
+ artifact.collect("*", "report/gcov/")
245
+ artifact.paths.coverage_report_gcov = "report/gcov"
246
+
247
+ class CoverageReportGcov(cls, CoverageReportGcovMixin):
248
+ @functools.wraps(cls.run)
249
+ def run(self, deps, tools):
250
+ super().run(deps, tools)
251
+ self.run_coverage_report_gcov(deps, tools)
252
+
253
+ def publish_coverage_data(self, artifact, tools):
254
+ CoverageReportGcovMixin.publish_coverage_data(self, artifact, tools)
255
+
256
+ @functools.wraps(cls.publish)
257
+ def publish(self, artifact, tools):
258
+ super().publish(artifact, tools)
259
+ self.publish_coverage_report_gcov(artifact, tools)
260
+ if bool(getattr(self, "gcov_coverage_data", coverage_data)):
261
+ self.publish_coverage_data(artifact, tools)
262
+
263
+ CoverageReportGcov.__doc__ = cls.__doc__
264
+ CoverageReportGcov.__name__ = cls.__name__
265
+ return CoverageReportGcov
266
+
267
+ return decorate
268
+
269
+ @staticmethod
270
+ def coverage_report_lcov(branches=True, demangle=True, html=True, coverage_data=False):
271
+ """Decorator generating HTML reports from collected coverage data.
272
+
273
+ A single lcov coverage information file is always generated
274
+ and published. The corresponding HTML report is optional but
275
+ enabled by default. Disabling can save time if the coverage
276
+ information have to be merged with info from other tasks in
277
+ order for an HTML report to be useful.
278
+
279
+ The decorator can be used standalone or in combination with
280
+ :func:`coverage_data`. In either case, gcov coverage data as
281
+ well as lcov coverage data is collected from dependency
282
+ artifacts and processed together with data generated by the
283
+ task itself.
284
+
285
+ The following class attributes may be set to control the behavior
286
+ of lcov:
287
+
288
+ - ``lcov_branches`` - Enable branch coverage. Sets lcov configs
289
+ ``branch_coverage=1`` and ``no_exception_branch=1``. Default: ``True``
290
+ - ``lcov_configs`` - List of lcov configs to set. Passed as ``--rc <config>``
291
+ on the command line. Default: ``[]``
292
+ - ``lcov_demangle`` - Display demangled function names in output.
293
+ Passed as ``--demangle-cpp`` on the command line. Default: ``True``
294
+ - ``lcov_exclude`` - List of files to exclude from the report.
295
+ Wildcards may be used. Passed as ``--exclude`` on the command line.
296
+ Default: ``[]``
297
+ - ``lcov_filters`` - List of filters. Passed as ``--filter`` on
298
+ the command line. Default: ``[brace, branch, range]``
299
+ - ``lcov_html`` - Enable/disable HTML report generation. Default: ``True``
300
+ - ``lcov_ignore_errors`` - List of errors to ignore.
301
+ Sets lcov config ``ignore_errors``. Default: ``[empty, mismatch, source, unused]``
302
+ - ``lcov_coverage_data`` - Republish raw gcov coverage data. Default: ``False``
303
+
304
+ The task artifact is annotated with:
305
+
306
+ - ``artifact.paths.coverage_report_lcov`` which points out the
307
+ path to the resulting coverage.info file in the artifact.
308
+ - ``artifact.paths.coverage_report_html`` which points out the
309
+ path to the resulting HTML files in the artifact.
310
+
311
+
312
+ A gcov and an lcov executable must exist in PATH. Set the ``GCOV``
313
+ and/or the ``LCOV`` environment variables to override their default names.
314
+
315
+ Args:
316
+ branches (boolean): Enable branch coverage. Default: ``True``.
317
+ Overridden by class attribute ``lcov_branches``, if set.
318
+ demangle (boolean): Display demangled function names in output.
319
+ Default: ``True``. Overridden by class attribute ``lcov_demangle``,
320
+ if set.
321
+ html (boolean): Generate an HTML report. Default: ``True``.
322
+ Overridden by class attribute ``lcov_html``, if set.
323
+ coverage_data (boolean): Republish coverage data found in
324
+ dependency artifacts. Default: ``False``
325
+ Overridden by class attribute ``lcov_coverage_data``, if set.
326
+
327
+ Example:
328
+
329
+ .. literalinclude:: ../examples/code_coverage/coverage.jolt
330
+ :language: python
331
+ :caption: examples/code_coverage/coverage.jolt
332
+
333
+ """
334
+
335
+ def decorate(cls):
336
+ class CoverageReportLcovMixin(object):
337
+ def _cov_report_has_trace_data(self, report):
338
+ """
339
+ Check if a coverage report has trace data.
340
+
341
+ LCOV argument ``--ignore-errors=empty`` is not working correctly.
342
+
343
+ """
344
+ with open(report, "r") as f:
345
+ for line in f:
346
+ if line.startswith("DA:") or line.startswith("BRDA:"):
347
+ return True
348
+ return False
349
+
350
+ def run_coverage_report_lcov(self, deps, tools):
351
+ lcov = tools.getenv("LCOV", "lcov")
352
+ if not tools.which(lcov):
353
+ raise_task_error(self, "lcov not found in PATH, cannot generate coverage report.")
354
+
355
+ gcov = tools.getenv("GCOV", tools.getenv("CROSS_COMPILE", "") + "gcov")
356
+ if not tools.which(gcov):
357
+ raise_task_error(self, "gcov not found in PATH, cannot generate coverage report.")
358
+
359
+ if bool(getattr(self, "lcov_demangle", demangle)):
360
+ cxxfilt = tools.getenv("CXXFILT", tools.getenv("CROSS_COMPILE", "") + "c++filt")
361
+ if not tools.which(cxxfilt):
362
+ raise_task_error(self, "c++filt not found in PATH, cannot generate coverage report.")
363
+
364
+ # Assume data is already copied by coverage_data() if the covdatadir exists,
365
+ # otherwise copy it from dependency artifacts.
366
+ if not hasattr(self, "covdatadir"):
367
+ self.covdatadir = tools.builddir("coverage-data")
368
+ for _, artifact in deps.items():
369
+ if artifact.paths.coverage_data:
370
+ tools.copy(str(artifact.paths.coverage_data), self.covdatadir)
371
+
372
+ reportdir = tools.builddir("coverage-report-lcov")
373
+ htmldir = tools.builddir("coverage-report-lcov-html")
374
+ cachedir = get_cachedir()
375
+
376
+ lcov_configs = list(getattr(self, "lcov_configs", []))
377
+ lcov_excludes = list(getattr(self, "lcov_excludes", []))
378
+ lcov_filters = list(getattr(self, "lcov_filters", ["brace", "branch", "range"]))
379
+ lcov_ignore_errors = list(getattr(self, "lcov_ignore_errors", ["empty", "mismatch", "source", "source", "unused"]))
380
+ lcov_substitutes = list(getattr(self, "lcov_substitutes", []))
381
+
382
+ lcov_html_flags = ["-p", tools.wsroot]
383
+ if bool(getattr(self, "lcov_demangle", demangle)):
384
+ lcov_html_flags.append("--demangle-cpp")
385
+ lcov_html_flags.append(f"--rc demangle_cpp={cxxfilt}")
386
+
387
+ lcov_html_flags.extend(getattr(self, "lcov_html_flags", []))
388
+ lcov_info_flags = getattr(self, "lcov_info_flags", [])
389
+
390
+ if bool(getattr(self, "lcov_branches", branches)):
391
+ lcov_configs.append("branch_coverage=1")
392
+ lcov_configs.append("no_exception_branch=1")
393
+
394
+ if lcov_excludes:
395
+ lcov_excludes = ["--exclude " + f"'{exclude}'" for exclude in lcov_excludes]
396
+ lcov_info_flags.extend(lcov_excludes)
397
+
398
+ if lcov_filters:
399
+ lcov_info_flags.append("--rc filter=" + ",".join(lcov_filters))
400
+ lcov_html_flags.append("--rc filter=" + ",".join(lcov_filters))
401
+
402
+ if lcov_ignore_errors:
403
+ lcov_html_flags.append("--rc ignore_errors=" + ",".join(lcov_ignore_errors))
404
+ lcov_info_flags.append("--rc ignore_errors=" + ",".join(lcov_ignore_errors))
405
+
406
+ if lcov_configs:
407
+ lcov_configs = ["--rc " + option for option in lcov_configs]
408
+ lcov_html_flags.extend(lcov_configs)
409
+ lcov_info_flags.extend(lcov_configs)
410
+
411
+ if lcov_substitutes:
412
+ lcov_substitutes = [f"--substitute '{sub}'" for sub in lcov_substitutes]
413
+ lcov_substitutes = tools.expand(lcov_substitutes)
414
+ lcov_html_flags.extend(lcov_substitutes)
415
+
416
+ self.info("Generating LCOV code coverage report")
417
+ tools.run("{} -b {} -c -d {covdatadir} -o {}/coverage.info --gcov-tool={} {}",
418
+ lcov, tools.wsroot, reportdir, gcov, " ".join(lcov_info_flags), output_on_error=True)
419
+
420
+ reports = []
421
+ for _, artifact in deps.items():
422
+ if artifact.paths.coverage_report_lcov:
423
+ reports.append(str(artifact.paths.coverage_report_lcov))
424
+ if reports:
425
+ filtered_reports = []
426
+ for report in reports:
427
+ if not self._cov_report_has_trace_data(report):
428
+ self.warning(f"Coverage report {report} has no data records, excluding from merge")
429
+ else:
430
+ filtered_reports.append(report)
431
+ reports = filtered_reports
432
+
433
+ if reports:
434
+ reports = ["-a " + report for report in reports]
435
+ self.info("Merging LCOV code coverage reports")
436
+ tools.run("{} {} -o {}/coverage.info --gcov-tool={} {}",
437
+ lcov, " ".join(reports), reportdir, gcov, " ".join(lcov_info_flags), output_on_error=True)
438
+
439
+ with tools.cwd(reportdir):
440
+ tools.replace_in_file("coverage.info", f"{tools.wsroot}/", "")
441
+ tools.replace_in_file("coverage.info", f"{cachedir}/", "{{cachedir}}/")
442
+ tools.copy("coverage.info", "coverage.info.abs")
443
+ tools.replace_in_file("coverage.info.abs", "SF:", f"SF:{tools.wsroot}/")
444
+ tools.replace_in_file("coverage.info.abs", f"SF:{tools.wsroot}/" + "{{cachedir}}/", f"SF:{cachedir}/")
445
+
446
+ if tools.file_size("coverage.info") <= 0 or not self._cov_report_has_trace_data(tools.expand_path("coverage.info")):
447
+ tools.unlink("coverage.info")
448
+ self.warning("No coverage data records available, skipping HTML report generation")
449
+ return
450
+
451
+ if bool(getattr(self, "lcov_html", html)):
452
+ self.info("Generating HTML code coverage report")
453
+ tools.run("genhtml {}/coverage.info.abs --output-directory {} --title '{short_qualified_name}' {}",
454
+ reportdir, htmldir, " ".join(lcov_html_flags), output_on_error=True)
455
+
456
+ @utils.cached.instance
457
+ def publish_coverage_data(self, artifact, tools):
458
+ if not bool(getattr(self, "lcov_coverage_data", coverage_data)):
459
+ return
460
+
461
+ self.verbose("Publishing coverage data from {covdatadir}")
462
+ with tools.cwd(self.covdatadir):
463
+ if artifact.collect("**/*.gc*", "cov/"):
464
+ artifact.paths.coverage_data = "cov"
465
+
466
+ def publish_coverage_report_lcov_info(self, artifact, tools):
467
+ reportdir = tools.builddir("coverage-report-lcov")
468
+ with tools.cwd(reportdir):
469
+ if artifact.collect("coverage.info", "report/lcov/"):
470
+ artifact.paths.coverage_report_lcov = "report/lcov/coverage.info"
471
+
472
+ def publish_coverage_report_lcov_html(self, artifact, tools):
473
+ htmldir = tools.builddir("coverage-report-lcov-html")
474
+ with tools.cwd(htmldir):
475
+ if artifact.collect("*", "report/html/"):
476
+ artifact.paths.coverage_report_html = "report/html"
477
+
478
+ class CoverageReportLcov(cls, CoverageReportLcovMixin):
479
+ @functools.wraps(cls.run)
480
+ def run(self, deps, tools):
481
+ super().run(deps, tools)
482
+ self.run_coverage_report_lcov(deps, tools)
483
+
484
+ def publish_coverage_data(self, artifact, tools):
485
+ CoverageReportLcovMixin.publish_coverage_data(self, artifact, tools)
486
+
487
+ @functools.wraps(cls.publish)
488
+ def publish(self, artifact, tools):
489
+ super().publish(artifact, tools)
490
+ self.publish_coverage_report_lcov_info(artifact, tools)
491
+ if bool(getattr(self, "lcov_html", html)):
492
+ self.publish_coverage_report_lcov_html(artifact, tools)
493
+ if bool(getattr(self, "lcov_coverage_data", coverage_data)):
494
+ self.publish_coverage_data(artifact, tools)
495
+
496
+ CoverageReportLcov.__doc__ = cls.__doc__
497
+ CoverageReportLcov.__name__ = cls.__name__
498
+ return CoverageReportLcov
499
+
500
+ return decorate
501
+
56
502
  @staticmethod
57
503
  def cxxflags(attrib, prepend=False):
58
504
  """
@@ -69,6 +515,22 @@ class attributes:
69
515
  """
70
516
  return utils.concat_attributes("cxxflags", attrib, prepend)
71
517
 
518
+ @staticmethod
519
+ def headers(attrib, prepend=False):
520
+ """
521
+ Decorates a task with an alternative ``headers`` attribute.
522
+
523
+ The new attribute will be concatenated with the regular
524
+ ``headers`` attribute.
525
+
526
+ Args:
527
+ attrib (str): Name of alternative attribute.
528
+ Keywords are expanded.
529
+ prepend (boolean): Prepend the value of the alternative
530
+ attribute. Default: false (append).
531
+ """
532
+ return utils.concat_attributes("headers", attrib, prepend)
533
+
72
534
  @staticmethod
73
535
  def incpaths(attrib, prepend=False):
74
536
  """
@@ -186,7 +648,7 @@ class Variable(HashInfluenceProvider):
186
648
 
187
649
  @staticmethod
188
650
  def __get_variables__(obj):
189
- variables = {}
651
+ variables = OrderedDict()
190
652
  for mro in reversed(obj.__class__.__mro__):
191
653
  for key, variable in getattr(mro, "__variable_list", {}).items():
192
654
  attr = getattr(obj.__class__, key)
@@ -197,7 +659,7 @@ class Variable(HashInfluenceProvider):
197
659
  def __set_name__(self, owner, name):
198
660
  self.name = name
199
661
  if "__variable_list" not in owner.__dict__:
200
- setattr(owner, "__variable_list", {})
662
+ setattr(owner, "__variable_list", OrderedDict())
201
663
  getattr(owner, "__variable_list")[name] = self
202
664
 
203
665
  def create(self, project, writer, deps, tools):
@@ -287,7 +749,7 @@ class ProjectVariable(Variable):
287
749
 
288
750
  def create(self, project, writer, deps, tools):
289
751
  value = getattr(project, self._attrib or self.name, "")
290
- if type(value) == list:
752
+ if type(value) is list:
291
753
  value = " ".join(value)
292
754
  writer.variable(self.name, str(value))
293
755
 
@@ -296,6 +758,54 @@ class ProjectVariable(Variable):
296
758
  return "PV: default={},attrib={}".format(self._default, self._attrib)
297
759
 
298
760
 
761
+ class StdVariable(Variable):
762
+ def __init__(self, lang, flagfmt, values, supported=None, default=None):
763
+ self._lang = lang
764
+ self._flagfmt = flagfmt
765
+ self._values = values
766
+ self._supported = supported or values
767
+ self._default = default
768
+
769
+ def create(self, project, writer, deps, tools):
770
+ value = getattr(project, self.name, self._default) or self._default
771
+ if not value:
772
+ return
773
+ raise_task_error_if(
774
+ type(value) is not int,
775
+ project,
776
+ f"Illegal {self._lang} language standard: '{value}'. Expected integer."
777
+ )
778
+ raise_task_error_if(
779
+ value not in self._values,
780
+ project,
781
+ f"Illegal {self._lang} language standard: '{value}'. Expected one of: {', '.join([str(v) for v in self._values])}"
782
+ )
783
+
784
+ if value not in self._supported:
785
+ new_value = min(self._supported, key=lambda n: abs(n - value))
786
+ project.warning(f"{self._lang}{value} is not supported. Using {self._lang}{new_value} instead.")
787
+ value = new_value
788
+
789
+ writer.variable(self.name, self._flagfmt.format(value))
790
+
791
+
792
+ class OptimizationVariable(Variable):
793
+ def __init__(self, default, values, name=None):
794
+ self.name = name
795
+ self._default = default
796
+ self._values = values
797
+ assert type(values) is dict, "Optimization values must be dict with compiler flag mapping"
798
+
799
+ def create(self, project, writer, deps, tools):
800
+ value = str(getattr(project, "optimize", self._default))
801
+ raise_task_error_if(
802
+ value not in self._values,
803
+ project,
804
+ f"Illegal value assigned to 'optimize' ({value}). Expected: 'none', 'debug', 'size', or 'release'."
805
+ )
806
+ writer.variable(self.name, self._values[value])
807
+
808
+
299
809
  class SharedLibraryVariable(Variable):
300
810
  def __init__(self, name=None, default=None):
301
811
  self.name = name
@@ -325,11 +835,11 @@ class GNUPCHVariables(Variable):
325
835
  "multiple precompiled headers found, only one is allowed")
326
836
 
327
837
  if len(pch) <= 0:
328
- writer.variable("pch_out", "$binary.dir/")
838
+ writer.variable("pch_out", tools.expand_relpath("{outdir}/{binary}.dir/", tools.wsroot))
329
839
  return
330
840
 
331
841
  project._pch = fs.path.basename(pch[0])
332
- project._pch_out = "$binary.dir/" + project._pch + self.gch_ext
842
+ project._pch_out = tools.expand_relpath("{outdir}/{binary}.dir/{_pch}" + self.gch_ext, tools.wsroot)
333
843
 
334
844
  writer.variable("pch", project._pch)
335
845
  writer.variable("pch_flags", "")
@@ -340,6 +850,14 @@ class GNUPCHVariables(Variable):
340
850
  return "PCHV"
341
851
 
342
852
 
853
+ class GNUCoverageVariable(Variable):
854
+ def create(self, project, writer, deps, tools):
855
+ if bool(getattr(project, "coverage", False)):
856
+ writer.variable("covflags", "--coverage")
857
+ else:
858
+ writer.variable("covflags", "")
859
+
860
+
343
861
  class Rule(HashInfluenceProvider):
344
862
  """ A source transformation rule.
345
863
 
@@ -372,7 +890,7 @@ class Rule(HashInfluenceProvider):
372
890
 
373
891
  """
374
892
 
375
- def __init__(self, command=None, infiles=None, outfiles=None, depfile=None, deps=None, variables=None, implicit=None, order_only=None, aggregate=False, phony=None):
893
+ def __init__(self, command=None, infiles=None, outfiles=None, depfile=None, deps=None, variables=None, implicit=None, implicit_outputs=None, order_only=None, aggregate=False, phony=None):
376
894
  """
377
895
  Creates a new rule.
378
896
 
@@ -443,12 +961,13 @@ class Rule(HashInfluenceProvider):
443
961
  before any C++ file is compiled.
444
962
  """
445
963
  self.command = command
446
- self.variables = variables or {}
964
+ self.variables = OrderedDict([(key, value) for key, value in (variables or {}).items()])
447
965
  self.depfile = depfile
448
966
  self.deps = deps
449
967
  self.infiles = infiles or []
450
968
  self.outfiles = utils.as_list(outfiles or [])
451
969
  self.implicit = implicit or []
970
+ self.implicit_outputs = implicit_outputs or []
452
971
  self.order_only = order_only or []
453
972
  self.aggregate = aggregate
454
973
  self.phony = phony
@@ -471,17 +990,17 @@ class Rule(HashInfluenceProvider):
471
990
  setattr(owner, "__rule_list", {})
472
991
  getattr(owner, "__rule_list")[name] = self
473
992
 
474
- def _out(self, project, infile):
993
+ def _out(self, project, infile, outfiles=None):
475
994
  in_dirname_outdir = None
476
995
  in_dirname, in_basename = fs.path.split(infile)
477
996
  in_base, in_ext = fs.path.splitext(in_basename)
478
997
 
479
998
  if in_dirname and fs.path.isabs(in_dirname):
480
- in_dirname_outdir = fs.path.relpath(in_dirname, project.outdir)
481
- in_dirname = fs.path.relpath(in_dirname, project.joltdir)
999
+ in_dirname_outdir = fs.path.relpath(in_dirname, project.tools.wsroot)
1000
+ in_dirname = fs.path.relpath(in_dirname, project.tools.wsroot)
482
1001
 
483
1002
  result_files = []
484
- for outfile in self.outfiles:
1003
+ for outfile in outfiles or self.outfiles:
485
1004
  outfile = project.tools.expand(
486
1005
  outfile,
487
1006
  in_path=in_dirname,
@@ -495,7 +1014,7 @@ class Rule(HashInfluenceProvider):
495
1014
 
496
1015
  result_files.append(outfile.replace("..", "__"))
497
1016
 
498
- result_vars = {}
1017
+ result_vars = OrderedDict()
499
1018
  for key, val in self.variables.items():
500
1019
  result_vars[key] = project.tools.expand(
501
1020
  val,
@@ -518,23 +1037,34 @@ class Rule(HashInfluenceProvider):
518
1037
  outfiles, _ = self._out(project, utils.as_list(infiles)[0])
519
1038
  return outfiles
520
1039
 
521
- def build(self, project, writer, infiles, implicit=None, order_only=None):
1040
+ def build(self, project, writer, infiles, implicit=None, implicit_outputs=None, order_only=None):
522
1041
  result = []
523
1042
  infiles = utils.as_list(infiles)
524
- infiles_rel = [fs.path.relpath(infile, project.outdir) for infile in infiles]
1043
+ infiles_rel = [fs.path.relpath(infile, project.tools.wsroot) for infile in infiles]
525
1044
  implicit = (self.implicit or []) + (implicit or [])
1045
+ implicit_outputs = (self.implicit_outputs or []) + (implicit_outputs or [])
526
1046
  order_only = (self.order_only or []) + (order_only or [])
527
1047
 
528
1048
  if self.aggregate:
529
1049
  outfiles, variables = self._out(project, infiles[0])
530
- outfiles_rel = [fs.path.relpath(outfile, project.outdir) for outfile in outfiles]
531
- writer.build(outfiles_rel, self.name, infiles_rel, variables=variables, implicit=implicit, order_only=self.order_only + order_only)
1050
+ outfiles_rel = [fs.path.relpath(outfile, project.tools.wsroot) for outfile in outfiles]
1051
+ if implicit_outputs:
1052
+ implicit_outfiles, _ = self._out(project, infiles[0], implicit_outputs)
1053
+ implicit_outfiles_rel = [fs.path.relpath(outfile, project.tools.wsroot) for outfile in implicit_outfiles]
1054
+ else:
1055
+ implicit_outfiles_rel = []
1056
+ writer.build(outfiles_rel, self.name, infiles_rel, variables=variables, implicit=implicit, implicit_outputs=implicit_outfiles_rel, order_only=self.order_only + order_only)
532
1057
  result.extend(outfiles)
533
1058
  else:
534
1059
  for infile, infile_rel in zip(infiles, infiles_rel):
535
1060
  outfiles, variables = self._out(project, infile)
536
- outfiles_rel = [fs.path.relpath(outfile, project.outdir) for outfile in outfiles]
537
- writer.build(outfiles_rel, self.name, infile_rel, variables=variables, implicit=implicit, order_only=order_only)
1061
+ outfiles_rel = [fs.path.relpath(outfile, project.tools.wsroot) for outfile in outfiles]
1062
+ if implicit_outputs:
1063
+ implicit_outfiles, _ = self._out(project, infile, implicit_outputs)
1064
+ implicit_outfiles_rel = [fs.path.relpath(outfile, project.tools.wsroot) for outfile in implicit_outfiles]
1065
+ else:
1066
+ implicit_outfiles_rel = []
1067
+ writer.build(outfiles_rel, self.name, infile_rel, variables=variables, implicit=implicit, implicit_outputs=implicit_outfiles_rel, order_only=order_only)
538
1068
  result.extend(outfiles)
539
1069
  if self.phony:
540
1070
  writer.build(self.phony, "phony", outfiles_rel)
@@ -564,12 +1094,12 @@ class ProtobufCompiler(Rule):
564
1094
  phony=True,
565
1095
  variables=None,
566
1096
  **kwargs):
567
- variables_final = {
568
- "desc": "[PROTOC] {in_base}{in_ext}",
569
- "out_depfile": "{binary}.dir/{in_base}.pb.d",
570
- "outdir_proto": os.path.dirname(outfiles[0]),
571
- "in_path_outdir": "{in_path_outdir}",
572
- }
1097
+ variables_final = OrderedDict([
1098
+ ("desc", "[PROTOC] {in_base}{in_ext}"),
1099
+ ("out_depfile", "{outdir_rel}/{binary}.dir/{in_base}.pb.d"),
1100
+ ("outdir_proto", "{outdir_rel}/{binary}.dir"),
1101
+ ("in_path_outdir", "{in_path_outdir}"),
1102
+ ])
573
1103
  variables_final.update(variables or {})
574
1104
  super().__init__(
575
1105
  command=command,
@@ -583,8 +1113,8 @@ class ProtobufCompiler(Rule):
583
1113
  **kwargs)
584
1114
  self.generator = generator
585
1115
 
586
- def _out(self, project, infile):
587
- outfiles, variables = super()._out(project, infile)
1116
+ def _out(self, project, infile, outfiles=None):
1117
+ outfiles, variables = super()._out(project, infile, outfiles)
588
1118
  variables["generator"] = project.tools.expand(self.generator)
589
1119
  return outfiles, variables
590
1120
 
@@ -610,8 +1140,8 @@ class GRPCProtobufCompiler(ProtobufCompiler):
610
1140
  variables=None,
611
1141
  **kwargs):
612
1142
  variables_final = {
613
- "out_depfile": "{binary}.dir/{in_base}.pb.d",
614
- "out_depfile_grpc": "{binary}.dir/{in_base}.grpc.pb.d",
1143
+ "out_depfile": "{outdir_rel}/{binary}.dir/{in_base}.pb.d",
1144
+ "out_depfile_grpc": "{outdir_rel}/{binary}.dir/{in_base}.grpc.pb.d",
615
1145
  }
616
1146
  variables_final.update(variables or {})
617
1147
  super().__init__(command=command, depfile=depfile, outfiles=outfiles, variables=variables_final, **kwargs)
@@ -650,8 +1180,8 @@ class FlatbufferCompiler(Rule):
650
1180
  **kwargs)
651
1181
  self.generator = generator
652
1182
 
653
- def _out(self, project, infile):
654
- outfiles, variables = super()._out(project, infile)
1183
+ def _out(self, project, infile, outfiles=None):
1184
+ outfiles, variables = super()._out(project, infile, outfiles)
655
1185
  variables["generator"] = project.tools.expand(self.generator)
656
1186
  return outfiles, variables
657
1187
 
@@ -678,7 +1208,8 @@ class Skip(Rule):
678
1208
 
679
1209
  @task_attributes.system
680
1210
  class MakeDirectory(Rule):
681
- command_linux = "mkdir -p $out"
1211
+ command_darwin = "mkdir -p $out"
1212
+ command_linux = command_darwin
682
1213
  command_windows = "if not exist $out mkdir $out"
683
1214
 
684
1215
  def __init__(self, name):
@@ -700,16 +1231,23 @@ class MakeDirectory(Rule):
700
1231
 
701
1232
  class GNUCompiler(Rule):
702
1233
  def __init__(self, *args, **kwargs):
1234
+ self.covfiles = kwargs.pop("covfiles", [])
703
1235
  super(GNUCompiler, self).__init__(*args, **kwargs)
704
1236
 
705
1237
  def create(self, project, writer, deps, tools):
706
1238
  super().create(project, writer, deps, tools)
707
1239
 
708
- def build(self, project, writer, infiles, implicit=None, order_only=None):
1240
+ def build(self, project, writer, infiles, implicit=None, implicit_outputs=None, order_only=None):
709
1241
  implicit = implicit or []
1242
+ implicit_outputs = implicit_outputs or []
1243
+
1244
+ if getattr(project, "coverage", False) and self.covfiles:
1245
+ implicit_outputs.extend(self.covfiles)
1246
+
710
1247
  if GNUPCHVariables.pch_ext not in self.infiles and project._pch_out is not None:
711
1248
  implicit.append(project._pch_out)
712
- return super().build(project, writer, infiles, implicit, order_only)
1249
+
1250
+ return super().build(project, writer, infiles, implicit=implicit, implicit_outputs=implicit_outputs, order_only=order_only)
713
1251
 
714
1252
  @utils.cached.instance
715
1253
  def get_influence(self, task):
@@ -746,7 +1284,9 @@ class FileListWriter(Rule):
746
1284
  def build(self, project, writer, infiles, implicit=None, order_only=None):
747
1285
  infiles = [fs.as_posix(infile) for infile in infiles] if self.posix else infiles
748
1286
  file_list_path = fs.path.join(project.outdir, "{0}.list".format(self.name))
1287
+ file_list_path = project.tools.expand_path(file_list_path)
749
1288
  file_list_hash_path = fs.path.join(project.outdir, "{0}.hash".format(self.name))
1289
+ file_list_hash_path = project.tools.expand_path(file_list_hash_path)
750
1290
  data, digest = self._data(project, infiles)
751
1291
  if not self._identical(file_list_path, file_list_hash_path, data, digest):
752
1292
  self._write(file_list_path, file_list_hash_path, data, digest)
@@ -829,11 +1369,12 @@ class GNUDepImporter(Rule):
829
1369
  for name, artifact in deps.items():
830
1370
  if artifact.cxxinfo.libpaths.items():
831
1371
  sandbox = project.tools.sandbox(artifact, project.incremental)
1372
+ sandbox = project.tools.expand_relpath(sandbox, project.tools.wsroot)
832
1373
  for lib in artifact.cxxinfo.libraries.items():
833
1374
  name = "{0}{1}{2}".format(self.prefix, lib, self.suffix)
834
1375
  for path in artifact.cxxinfo.libpaths.items():
835
1376
  archive = fs.path.join(sandbox, path, name)
836
- if fs.path.exists(archive):
1377
+ if fs.path.exists(os.path.join(project.tools.wsroot, archive)):
837
1378
  archives.append(archive)
838
1379
  return archives
839
1380
 
@@ -844,7 +1385,9 @@ class GNUDepImporter(Rule):
844
1385
  if isinstance(project, CXXLibrary):
845
1386
  imports += self._build_archives(project, writer, deps)
846
1387
  if not project.shared and project.selfsustained:
847
- writer.sources.extend(imports)
1388
+ sources = [os.path.join(project.tools.wsroot, source) for source in imports]
1389
+ sources = [os.path.relpath(source, project.joltdir) for source in sources]
1390
+ writer.sources.extend(sources)
848
1391
  return imports
849
1392
 
850
1393
  def get_influence(self, task):
@@ -857,7 +1400,7 @@ class Toolchain(object):
857
1400
 
858
1401
  @staticmethod
859
1402
  def build_rules_and_vars(obj):
860
- rule_map = {}
1403
+ rule_map = OrderedDict()
861
1404
  rules, vars = Toolchain.all_rules_and_vars(obj)
862
1405
  for name, rule in rules.items():
863
1406
  rule.name = name
@@ -929,18 +1472,18 @@ class IncludePaths(Variable):
929
1472
  return tools.expand(path)
930
1473
  if path[0] in ['-']:
931
1474
  path = tools.expand_path(path[1:])
932
- return tools.expand_relpath(path, project.outdir)
1475
+ return tools.expand_relpath(path, project.tools.wsroot)
933
1476
 
934
1477
  def expand_artifact(sandbox, path):
935
1478
  if path[0] in ['=', fs.sep]:
936
1479
  return path
937
1480
  if path[0] in ['-']:
938
1481
  path = fs.path.join(project.joltdir, path[1:])
939
- return tools.expand_relpath(fs.path.join(sandbox, path), project.outdir)
1482
+ return tools.expand_relpath(fs.path.join(sandbox, path), project.tools.wsroot)
940
1483
 
941
1484
  incpaths = []
942
1485
  if self.outdir:
943
- incpaths += ["$binary.dir"]
1486
+ incpaths += [tools.expand_relpath("{outdir}/{binary}.dir", project.tools.wsroot)]
944
1487
  if self.attrib:
945
1488
  incpaths += [expand(path) for path in getattr(project, self.attrib)]
946
1489
  if self.imported:
@@ -969,12 +1512,13 @@ class LibraryPaths(Variable):
969
1512
  return
970
1513
  libpaths = []
971
1514
  if self.attrib:
972
- libpaths = [tools.expand_relpath(path, project.outdir) for path in getattr(project, self.attrib)]
1515
+ libpaths = [tools.expand_relpath(path, project.tools.wsroot) for path in getattr(project, self.attrib)]
973
1516
  if self.imported:
974
1517
  for _, artifact in deps.items():
975
1518
  libs = artifact.cxxinfo.libpaths.items()
976
1519
  if libs:
977
1520
  sandbox = tools.sandbox(artifact, project.incremental)
1521
+ sandbox = tools.expand_relpath(sandbox, project.tools.wsroot)
978
1522
  libpaths += [fs.path.join(sandbox, path) for path in libs]
979
1523
  libpaths = ["{0}{1}".format(self.prefix, path) for path in libpaths]
980
1524
  writer.variable(self.name, " ".join(libpaths))
@@ -1036,7 +1580,9 @@ class GNUToolchain(Toolchain):
1036
1580
  bin = Skip(infiles=[".dll", ".elf", ".exe", ".out", ".so"])
1037
1581
 
1038
1582
  joltdir = ProjectVariable()
1583
+ builddir = ProjectVariable(attrib="outdir")
1039
1584
  outdir = ProjectVariable()
1585
+ outdir_rel = ProjectVariable()
1040
1586
  binary = ProjectVariable()
1041
1587
 
1042
1588
  ar = ToolEnvironmentVariable(default="ar", abspath=True)
@@ -1049,7 +1595,17 @@ class GNUToolchain(Toolchain):
1049
1595
  cxxwrap = EnvironmentVariable(default="")
1050
1596
  flatc = ToolEnvironmentVariable(default="flatc", envname="FLATC", abspath=True)
1051
1597
  protoc = ToolEnvironmentVariable(default="protoc", envname="PROTOC", abspath=True)
1052
-
1598
+ strip = ToolEnvironmentVariable(default="strip", envname="STRIP", abspath=True)
1599
+
1600
+ cstd = StdVariable("C", "-std=c{}", values=c_standards_list, default=c_standard_default)
1601
+ cxxstd = StdVariable("C++", "-std=c++{}", values=cxx_standards_list, default=cxx_standard_default)
1602
+ optflag = OptimizationVariable(default="release", values={
1603
+ "none": "-O0",
1604
+ "debug": "-Og",
1605
+ "size": "-Os",
1606
+ "release": "-O2",
1607
+ None: "-O0",
1608
+ })
1053
1609
  asflags = EnvironmentVariable(default="")
1054
1610
  cflags = EnvironmentVariable(default="")
1055
1611
  cxxflags = EnvironmentVariable(default="")
@@ -1065,6 +1621,8 @@ class GNUToolchain(Toolchain):
1065
1621
  extra_cxxflags = ProjectVariable(attrib="cxxflags")
1066
1622
  extra_ldflags = ProjectVariable(attrib="ldflags")
1067
1623
 
1624
+ covflags = GNUCoverageVariable()
1625
+
1068
1626
  task_fbflags = ProjectVariable(attrib="fbflags")
1069
1627
  task_protoflags = ProjectVariable(attrib="protoflags")
1070
1628
 
@@ -1075,31 +1633,34 @@ class GNUToolchain(Toolchain):
1075
1633
  libpaths = LibraryPaths(prefix="-L")
1076
1634
  libraries = Libraries(prefix="-l")
1077
1635
 
1078
- mkdir_debug = MakeDirectory(name=".debug")
1636
+ mkdir_debug = MakeDirectory(name="$outdir_rel/.debug")
1079
1637
 
1080
1638
  compile_pch = GNUCompiler(
1081
- command="$cxxwrap $cxx -x c++-header $cxxflags $shared_flags $imported_cxxflags $extra_cxxflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1639
+ command="$cxxwrap $cxx -x c++-header $cxxstd $optflag $cxxflags $shared_flags $imported_cxxflags $extra_cxxflags $covflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1082
1640
  deps="gcc",
1083
1641
  depfile="$out.d",
1084
1642
  infiles=[GNUPCHVariables.pch_ext],
1085
1643
  outfiles=["{outdir}/{binary}.dir/{in_base}{in_ext}" + GNUPCHVariables.gch_ext],
1644
+ covfiles=["{outdir}/{binary}.dir/{in_base}{in_ext}.gcno"],
1086
1645
  variables={"desc": "[PCH] {in_base}{in_ext}"})
1087
1646
 
1088
1647
  compile_c = GNUCompiler(
1089
- command="$ccwrap $cc -x c $pch_flags $cflags $shared_flags $imported_cflags $extra_cflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1648
+ command="$ccwrap $cc -x c $cstd $pch_flags $optflag $cflags $shared_flags $imported_cflags $extra_cflags $covflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1090
1649
  deps="gcc",
1091
1650
  depfile="$out.d",
1092
1651
  infiles=[".c"],
1093
1652
  outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.o"],
1653
+ covfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.gcno"],
1094
1654
  variables={"desc": "[C] {in_base}{in_ext}"},
1095
1655
  implicit=["$cc_path"])
1096
1656
 
1097
1657
  compile_cxx = GNUCompiler(
1098
- command="$cxxwrap $cxx -x c++ $pch_flags $cxxflags $shared_flags $imported_cxxflags $extra_cxxflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1658
+ command="$cxxwrap $cxx -x c++ $cxxstd $pch_flags $optflag $cxxflags $shared_flags $imported_cxxflags $extra_cxxflags $covflags $macros $incpaths -MMD -MF $out.d -c $in -o $out",
1099
1659
  deps="gcc",
1100
1660
  depfile="$out.d",
1101
1661
  infiles=[".cc", ".cpp", ".cxx"],
1102
1662
  outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.o"],
1663
+ covfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.gcno"],
1103
1664
  variables={"desc": "[CXX] {in_base}{in_ext}"},
1104
1665
  implicit=["$cxx_path"])
1105
1666
 
@@ -1109,6 +1670,7 @@ class GNUToolchain(Toolchain):
1109
1670
  depfile="$out.d",
1110
1671
  infiles=[".s", ".asm"],
1111
1672
  outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.o"],
1673
+ covfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.gcno"],
1112
1674
  variables={"desc": "[ASM] {in_base}{in_ext}"},
1113
1675
  implicit=["$cc_path"])
1114
1676
 
@@ -1118,6 +1680,7 @@ class GNUToolchain(Toolchain):
1118
1680
  depfile="$out.d",
1119
1681
  infiles=[".S"],
1120
1682
  outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.o"],
1683
+ covfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.gcno"],
1121
1684
  variables={"desc": "[ASM] {in_base}{in_ext}"},
1122
1685
  implicit=["$cc_path"])
1123
1686
 
@@ -1127,51 +1690,100 @@ class GNUToolchain(Toolchain):
1127
1690
 
1128
1691
  linker = GNULinker(
1129
1692
  command=" && ".join([
1130
- "$ld $ldflags $imported_ldflags $extra_ldflags $libpaths -Wl,--start-group @objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1131
- "$objcopy_path --only-keep-debug $out .debug/$binary",
1693
+ "$ld $ldflags $imported_ldflags $extra_ldflags $covflags $libpaths -Wl,--start-group @$outdir_rel/objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1694
+ "$objcopy_path --only-keep-debug $out $outdir_rel/.debug/$binary",
1132
1695
  "$objcopy_path --strip-all $out",
1133
- "$objcopy_path --add-gnu-debuglink=.debug/$binary $out"
1696
+ "$objcopy_path --add-gnu-debuglink=$outdir_rel/.debug/$binary $out"
1134
1697
  ]),
1135
1698
  infiles=[".o", ".obj", ".a"],
1136
1699
  outfiles=["{outdir}/{binary}"],
1137
1700
  variables={"desc": "[LINK] {binary}"},
1138
- implicit=["$ld_path", "$objcopy_path", ".debug"])
1701
+ implicit=["$ld_path", "$objcopy_path", "$outdir_rel/.debug"])
1139
1702
 
1140
1703
  dynlinker = GNULinker(
1141
1704
  command=" && ".join([
1142
- "$ld $ldflags -shared $imported_ldflags $extra_ldflags $libpaths -Wl,--start-group @objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1143
- "$objcopy_path --only-keep-debug $out .debug/lib$binary.so",
1705
+ "$ld $ldflags -shared $imported_ldflags $extra_ldflags $covflags $libpaths -Wl,--start-group @$outdir_rel/objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1706
+ "$objcopy_path --only-keep-debug $out $outdir_rel/.debug/lib$binary.so",
1144
1707
  "$objcopy_path --strip-all $out",
1145
- "$objcopy_path --add-gnu-debuglink=.debug/lib$binary.so $out"
1708
+ "$objcopy_path --add-gnu-debuglink=$outdir_rel/.debug/lib$binary.so $out"
1146
1709
  ]),
1147
1710
  infiles=[".o", ".obj", ".a"],
1148
1711
  outfiles=["{outdir}/lib{binary}.so"],
1149
1712
  variables={"desc": "[LINK] {binary}"},
1150
- implicit=["$ld_path", "$objcopy_path", ".debug"])
1713
+ implicit=["$ld_path", "$objcopy_path", "$outdir_rel/.debug"])
1151
1714
 
1152
1715
  archiver = GNUArchiver(
1153
- command="$ar -M < objects.list && $ranlib $out",
1716
+ command="$ar -M < $outdir_rel/objects.list && $ranlib $out",
1154
1717
  infiles=[".o", ".obj", ".a"],
1155
1718
  outfiles=["{outdir}/lib{binary}.a"],
1156
1719
  variables={"desc": "[AR] lib{binary}.a"},
1157
- implicit=["$ld_path", "$ar_path"])
1720
+ implicit=["$ld_path", "$ar_path", "$ranlib_path"])
1158
1721
 
1159
1722
  depimport = GNUDepImporter(
1160
1723
  prefix="lib",
1161
1724
  suffix=".a")
1162
1725
 
1163
1726
 
1727
+ class BSDArchiver(Rule):
1728
+ def __init__(self, *args, **kwargs):
1729
+ super(BSDArchiver, self).__init__(*args, aggregate=True, **kwargs)
1730
+
1731
+ def build(self, project, writer, infiles, implicit=None, order_only=None):
1732
+ writer._objects = infiles
1733
+ project._binaries, _ = self._out(project, project.binary)
1734
+ file_list = FileListWriter("objects", project._binaries)
1735
+ file_list.build(project, writer, infiles)
1736
+ super().build(project, writer, infiles, implicit=writer.depimports, order_only=order_only)
1737
+
1738
+ def get_influence(self, task):
1739
+ return "BSDArchiver" + super().get_influence(task)
1740
+
1741
+
1742
+ class DarwinGNUToolchain(GNUToolchain):
1743
+ libtool = ToolEnvironmentVariable(default="libtool", envname="LIBTOOL", abspath=True)
1744
+ dsymutil = ToolEnvironmentVariable(default="dsymutil", envname="DSYMUTIL", abspath=True)
1745
+
1746
+ linker = GNULinker(
1747
+ command=" && ".join([
1748
+ "$ld $ldflags $imported_ldflags $extra_ldflags $covflags $libpaths @$outdir_rel/objects.list -o $out $libraries",
1749
+ "$dsymutil $out -o $outdir_rel/.debug/$binary.dSYM",
1750
+ "$strip $out",
1751
+ ]),
1752
+ infiles=[".o", ".obj", ".a"],
1753
+ outfiles=["{outdir}/{binary}"],
1754
+ variables={"desc": "[LINK] {binary}"},
1755
+ implicit=["$ld_path", "$dsymutil_path", "$strip_path", "$outdir_rel/.debug"])
1756
+
1757
+ dynlinker = GNULinker(
1758
+ command=" && ".join([
1759
+ "$ld $ldflags -shared $imported_ldflags $extra_ldflags $covflags $libpaths @$outdir_rel/objects.list -o $out $libraries",
1760
+ "$dsymutil $out -o $outdir_rel/.debug/$binary.dSYM",
1761
+ "$strip $out",
1762
+ ]),
1763
+ infiles=[".o", ".obj", ".a"],
1764
+ outfiles=["{outdir}/lib{binary}.so"],
1765
+ variables={"desc": "[LINK] {binary}"},
1766
+ implicit=["$ld_path", "$dsymutil_path", "$strip_path", "$outdir_rel/.debug"])
1767
+
1768
+ archiver = BSDArchiver(
1769
+ command="$libtool -static -o $out @$outdir_rel/objects.list && $ranlib $out",
1770
+ infiles=[".o", ".obj", ".a"],
1771
+ outfiles=["{outdir}/lib{binary}.a"],
1772
+ variables={"desc": "[AR] lib{binary}.a"},
1773
+ implicit=["$libtool_path", "$ranlib_path"])
1774
+
1775
+
1164
1776
  class MinGWToolchain(GNUToolchain):
1165
1777
  linker = GNULinker(
1166
1778
  command=" && ".join([
1167
- "$ld $ldflags $imported_ldflags $extra_ldflags $libpaths -Wl,--start-group @objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1168
- "$objcopy --only-keep-debug $out .debug/$binary.exe",
1779
+ "$ld $ldflags $imported_ldflags $extra_ldflags $libpaths -Wl,--start-group @$outdir_rel/objects.list -Wl,--end-group -o $out -Wl,--start-group $libraries -Wl,--end-group",
1780
+ "$objcopy --only-keep-debug $out $outdir_rel/.debug/$binary.exe",
1169
1781
  "$objcopy --strip-all $out",
1170
- "$objcopy --add-gnu-debuglink=.debug/$binary.exe $out"
1782
+ "$objcopy --add-gnu-debuglink=$outdir_rel/.debug/$binary.exe $out"
1171
1783
  ]),
1172
1784
  outfiles=["{outdir}/{binary}.exe"],
1173
1785
  variables={"desc": "[LINK] {binary}"},
1174
- implicit=["$ld_path", "$objcopy_path", ".debug"])
1786
+ implicit=["$ld_path", "$objcopy_path", "$outdir_rel/.debug"])
1175
1787
 
1176
1788
 
1177
1789
  class MSVCArchiver(Rule):
@@ -1189,6 +1801,62 @@ class MSVCArchiver(Rule):
1189
1801
  return "MSVCArchiver" + super().get_influence(task)
1190
1802
 
1191
1803
 
1804
+ class MSVCCRT(Variable):
1805
+ def __init__(self, default="/MD", flagsfn=None):
1806
+ self.default = default
1807
+ self.flagsfn = flagsfn
1808
+
1809
+ def _combine(self, project, crt1, crt2):
1810
+ if crt1 is None:
1811
+ return crt2
1812
+ if crt2 is None:
1813
+ return crt1
1814
+ raise_task_error_if(crt1 != crt2, project, "Mismatching Windows CRT library types (/MT vs /MD)")
1815
+ return crt1
1816
+
1817
+ def _select_flag(self, flags):
1818
+ """ Look for /Mxx flag in the list of flags """
1819
+ if flags is None:
1820
+ return None
1821
+ if "Static" in flags:
1822
+ return "/MT"
1823
+ if "StaticDebug" in flags:
1824
+ return "/MTd"
1825
+ if "Dynamic" in flags:
1826
+ return "/MD"
1827
+ if "DynamicDebug" in flags:
1828
+ return "/MDd"
1829
+ if "/MT" in flags:
1830
+ return "/MT"
1831
+ if "/MTd" in flags:
1832
+ return "/MTd"
1833
+ if "/MD" in flags:
1834
+ return "/MD"
1835
+ if "/MDd" in flags:
1836
+ return "/MDd"
1837
+ return None
1838
+
1839
+ def _select_dep_flags(self, project, dep):
1840
+ crt = self._select_flag(dep.cxxinfo.asflags.items())
1841
+ if hasattr(dep.cxxinfo, "crt"):
1842
+ crt = self._combine(project, crt, self._select_flag([str(dep.cxxinfo.crt)]))
1843
+ crt = self._combine(project, crt, self._select_flag(dep.cxxinfo.cflags.items()))
1844
+ return self._combine(project, crt, self._select_flag(dep.cxxinfo.cxxflags.items()))
1845
+
1846
+ def create(self, project, writer, deps, tools):
1847
+ crt = self._select_flag(getattr(project, "crt", None))
1848
+ crt = self._combine(project, crt, self._select_flag(project._asflags()))
1849
+ crt = self._combine(project, crt, self._select_flag(project._cflags()))
1850
+ crt = self._combine(project, crt, self._select_flag(project._cxxflags()))
1851
+ for _, artifact in deps.items():
1852
+ crt = self._combine(project, crt, self._select_dep_flags(project, artifact))
1853
+ writer.variable(self.name, crt or self.default)
1854
+
1855
+ @utils.cached.instance
1856
+ def get_influence(self, task):
1857
+ return "CRT"
1858
+
1859
+
1192
1860
  MSVCCompiler = GNUCompiler
1193
1861
  MSVCLinker = GNULinker
1194
1862
  MSVCDepImporter = GNUDepImporter
@@ -1199,7 +1867,9 @@ class MSVCToolchain(Toolchain):
1199
1867
  bin = Skip(infiles=[".dll", ".exe"])
1200
1868
 
1201
1869
  joltdir = ProjectVariable()
1870
+ builddir = ProjectVariable(attrib="outdir")
1202
1871
  outdir = ProjectVariable()
1872
+ outdir_rel = ProjectVariable()
1203
1873
  binary = ProjectVariable()
1204
1874
 
1205
1875
  cl = ToolEnvironmentVariable(default="cl", envname="cl_exe", abspath=True)
@@ -1208,13 +1878,25 @@ class MSVCToolchain(Toolchain):
1208
1878
  flatc = ToolEnvironmentVariable(default="flatc", envname="FLATC", abspath=True)
1209
1879
  protoc = ToolEnvironmentVariable(default="protoc", envname="PROTOC", abspath=True)
1210
1880
 
1881
+ cstd = StdVariable("C", "/std:c{}", values=c_standards_list, supported=[11, 17], default=c_standard_default)
1882
+ cxxstd = StdVariable("C++", "/std:c++{}", values=cxx_standards_list, supported=[14, 17, 20], default=cxx_standard_default)
1883
+ optflag = OptimizationVariable(default="speed", values={
1884
+ "none": "/Od",
1885
+ "debug": "/Od /Zi",
1886
+ "size": "/O1",
1887
+ "release": "/O2 /DNDEBUG",
1888
+ None: "/0d",
1889
+ })
1890
+ win32flags = Variable("/DWIN32 /D_WINDOWS /D_WIN32_WINNT=0x0601 /EHsc")
1211
1891
  asflags = EnvironmentVariable(default="")
1212
- cflags = EnvironmentVariable(default="/EHsc")
1213
- cxxflags = EnvironmentVariable(default="/EHsc")
1892
+ cflags = EnvironmentVariable(default="")
1893
+ cxxflags = EnvironmentVariable(default="")
1214
1894
  fbflags = EnvironmentVariable(default="")
1215
1895
  ldflags = EnvironmentVariable(default="")
1216
1896
  protoflags = EnvironmentVariable(default="")
1217
1897
 
1898
+ crt = MSVCCRT()
1899
+
1218
1900
  extra_asflags = ProjectVariable(attrib="asflags")
1219
1901
  extra_cflags = ProjectVariable(attrib="cflags")
1220
1902
  extra_cxxflags = ProjectVariable(attrib="cxxflags")
@@ -1229,26 +1911,26 @@ class MSVCToolchain(Toolchain):
1229
1911
  libraries = Libraries(suffix=".lib")
1230
1912
 
1231
1913
  compile_asm = MSVCCompiler(
1232
- command="$cl /nologo /showIncludes $asflags $extra_asflags $macros $incpaths /c /Tc$in /Fo$out",
1914
+ command="$cl /nologo /showIncludes $crt $win32flags $asflags $extra_asflags $macros $incpaths /c /Tc$in /Fo$out",
1233
1915
  deps="msvc",
1234
1916
  infiles=[".asm", ".s", ".S"],
1235
- outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}.obj"],
1917
+ outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.obj"],
1236
1918
  variables={"desc": "[ASM] {in_base}{in_ext}"},
1237
1919
  implicit=["$cl_path"])
1238
1920
 
1239
1921
  compile_c = MSVCCompiler(
1240
- command="$cl /nologo /showIncludes $cxxflags $extra_cxxflags $macros $incpaths /c /Tc$in /Fo$out",
1922
+ command="$cl /nologo /showIncludes $cstd $crt $win32flags $optflag $cflags $extra_cflags $macros $incpaths /c /Tc$in /Fo$out",
1241
1923
  deps="msvc",
1242
1924
  infiles=[".c"],
1243
- outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}.obj"],
1925
+ outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.obj"],
1244
1926
  variables={"desc": "[C] {in_base}{in_ext}"},
1245
1927
  implicit=["$cl_path"])
1246
1928
 
1247
1929
  compile_cxx = MSVCCompiler(
1248
- command="$cl /nologo /showIncludes $cxxflags $extra_cxxflags $macros $incpaths /c /Tp$in /Fo$out",
1930
+ command="$cl /nologo /showIncludes $cxxstd $crt $win32flags $optflag $cxxflags $extra_cxxflags $macros $incpaths /c /Tp$in /Fo$out",
1249
1931
  deps="msvc",
1250
1932
  infiles=[".cc", ".cpp", ".cxx"],
1251
- outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}.obj"],
1933
+ outfiles=["{outdir}/{binary}.dir/{in_path}/{in_base}{in_ext}.obj"],
1252
1934
  variables={"desc": "[CXX] {in_base}{in_ext}"},
1253
1935
  implicit=["$cl_path"])
1254
1936
 
@@ -1256,14 +1938,14 @@ class MSVCToolchain(Toolchain):
1256
1938
  compile_proto = ProtobufCompiler(generator="cpp")
1257
1939
 
1258
1940
  linker = MSVCLinker(
1259
- command="$link /nologo $ldflags $extra_ldflags $libpaths @objects.list $libraries /out:$out",
1941
+ command="$link /nologo $ldflags $extra_ldflags $libpaths @$outdir_rel/objects.list $libraries /out:$out",
1260
1942
  infiles=[".o", ".obj", ".lib"],
1261
1943
  outfiles=["{outdir}/{binary}.exe"],
1262
1944
  variables={"desc": "[LINK] {binary}"},
1263
1945
  implicit=["$link_path"])
1264
1946
 
1265
1947
  archiver = MSVCArchiver(
1266
- command="$lib /nologo /out:$out @objects.list",
1948
+ command="$lib /nologo /out:$out @$outdir_rel/objects.list",
1267
1949
  infiles=[".o", ".obj", ".lib"],
1268
1950
  outfiles=["{outdir}/{binary}.lib"],
1269
1951
  variables={"desc": "[LIB] {binary}"},
@@ -1276,11 +1958,15 @@ class MSVCToolchain(Toolchain):
1276
1958
 
1277
1959
  _toolchains = {
1278
1960
  GNUToolchain: GNUToolchain(),
1961
+ DarwinGNUToolchain: DarwinGNUToolchain(),
1279
1962
  MSVCToolchain: MSVCToolchain(),
1280
1963
  }
1281
1964
 
1282
- if os.name == "nt":
1965
+ _system = platform.system()
1966
+ if _system == "Windows":
1283
1967
  toolchain = _toolchains[MSVCToolchain]
1968
+ elif _system == "Darwin":
1969
+ toolchain = _toolchains[DarwinGNUToolchain]
1284
1970
  else:
1285
1971
  toolchain = _toolchains[GNUToolchain]
1286
1972
 
@@ -1320,9 +2006,23 @@ class CXXProject(Task):
1320
2006
  cflags = []
1321
2007
  """ A list of compiler flags used when compiling C files. """
1322
2008
 
2009
+ cstd = None
2010
+ """
2011
+ C language standard to use (int). Default: 17
2012
+
2013
+ If the chosen standard is not supported, the nearest supported standard is selected.
2014
+ """
2015
+
1323
2016
  cxxflags = []
1324
2017
  """ A list of compiler flags used when compiling C++ files. """
1325
2018
 
2019
+ cxxstd = None
2020
+ """
2021
+ C++ language standard to use (int). Default: 17
2022
+
2023
+ If the chosen standard is not supported, the nearest supported standard is selected.
2024
+ """
2025
+
1326
2026
  depimports = []
1327
2027
  """ List of implicit dependencies """
1328
2028
 
@@ -1341,6 +2041,18 @@ class CXXProject(Task):
1341
2041
  macros = []
1342
2042
  """ List of preprocessor macros to set """
1343
2043
 
2044
+ optimize = "release"
2045
+ """
2046
+ Compiler optimization level.
2047
+
2048
+ Supported values are:
2049
+
2050
+ - "none" / None
2051
+ - "debug"
2052
+ - "size"
2053
+ - "release"
2054
+ """
2055
+
1344
2056
  sources = []
1345
2057
  """ A list of sources to compile.
1346
2058
 
@@ -1374,6 +2086,33 @@ class CXXProject(Task):
1374
2086
  binary = None
1375
2087
  """ Name of the target binary (defaults to canonical task name) """
1376
2088
 
2089
+ coverage = False
2090
+ """Enable code coverage instrumentation.
2091
+
2092
+ Only implemented for GCC/Clang toolchains.
2093
+
2094
+ When set, the --coverage flag is passed to the compiler. The compiler
2095
+ instruments the generated machine code and outputs coverage note files
2096
+ (.gcno), one for each translation unit, which are collected into the
2097
+ task artifact. Upon running the executable, coverage data files are
2098
+ generated.
2099
+
2100
+ Use the :func:`attributes.coverage_data` decorator to
2101
+ automatically collect and publish data files in tasks that run
2102
+ instrumented binaries. The :func:`attributes.coverage_report_gcov`
2103
+ decorator can then be used to process the notes and data files
2104
+ into human readable coverage information. There is also a
2105
+ :func:`attributes.coverage_report_lcov` decorator that will
2106
+ generate and publish an HTML coverage report.
2107
+
2108
+ Example:
2109
+
2110
+ .. literalinclude:: ../examples/code_coverage/coverage.jolt
2111
+ :language: python
2112
+ :caption: examples/code_coverage/coverage.jolt
2113
+
2114
+ """
2115
+
1377
2116
  incremental = True
1378
2117
  """ Compile incrementally.
1379
2118
 
@@ -1428,7 +2167,7 @@ class CXXProject(Task):
1428
2167
  self._init_rules_and_vars()
1429
2168
 
1430
2169
  def _init_rules_and_vars(self):
1431
- self._rules_by_ext = {}
2170
+ self._rules_by_ext = OrderedDict()
1432
2171
  self._rules = []
1433
2172
  self._variables = []
1434
2173
 
@@ -1466,22 +2205,26 @@ class CXXProject(Task):
1466
2205
  def _verify_influence(self, deps, artifact, tools):
1467
2206
  # Verify that listed sources and their dependencies are influencing
1468
2207
  sources = set(self.sources + getattr(self, "headers", []))
1469
- with tools.cwd(self.outdir):
2208
+ with tools.cwd(tools.wsroot):
1470
2209
  depfiles = [obj + ".d" for obj in getattr(self._writer, "_objects", [])]
1471
2210
  for depfile in depfiles:
1472
2211
  try:
1473
2212
  data = tools.read_file(depfile)
1474
2213
  except Exception:
1475
2214
  continue
1476
- data = data.split(":", 1)
1477
- if len(data) <= 1:
1478
- continue
1479
2215
 
1480
- depsrcs = data[1]
1481
- depsrcs = depsrcs.split()
1482
- depsrcs = [f.rstrip("\\").strip() for f in depsrcs]
1483
- depsrcs = [tools.expand_relpath(dep, self.joltdir) for dep in filter(lambda n: n, depsrcs)]
1484
- sources = sources.union(depsrcs)
2216
+ data = data.replace("\\\n", "")
2217
+ for depline in data.splitlines():
2218
+ depline = depline.strip()
2219
+ depline = depline.split(":", 1)
2220
+ if len(depline) <= 1:
2221
+ continue
2222
+
2223
+ inputs = depline[1]
2224
+ inputs = inputs.replace("\\ ", "\x00")
2225
+ inputs = [dep.strip().replace("\x00", " ") for dep in inputs.split()]
2226
+ inputs = [tools.expand_relpath(input, self.joltdir) for input in filter(lambda n: n, inputs)]
2227
+ sources = sources.union(inputs)
1485
2228
  super()._verify_influence(deps, artifact, tools, sources)
1486
2229
 
1487
2230
  def _expand_headers(self):
@@ -1515,10 +2258,11 @@ class CXXProject(Task):
1515
2258
  self.sources = sources
1516
2259
 
1517
2260
  def _write_ninja_file(self, basedir, deps, tools, filename="build.ninja"):
1518
- with open(fs.path.join(basedir, filename), "w") as fobj:
2261
+ with open(tools.expand_path(fs.path.join(basedir, filename)), "w") as fobj:
1519
2262
  writer = ninja.Writer(fobj)
1520
- writer.depimports = [tools.expand_relpath(dep, self.outdir)
1521
- for dep in self.depimports]
2263
+ writer.depimports = [
2264
+ tools.expand_relpath(dep, tools.wsroot)
2265
+ for dep in self.depimports]
1522
2266
  writer.objects = []
1523
2267
  writer.sources = copy.copy(self.sources)
1524
2268
  self._populate_rules_and_variables(writer, deps, tools)
@@ -1526,12 +2270,6 @@ class CXXProject(Task):
1526
2270
  writer.close()
1527
2271
  return writer
1528
2272
 
1529
- def _prepare_ninja_cache(self, deps, tools):
1530
- """ Hooked from ninja-cache plugin """
1531
-
1532
- def _write_ninja_cache(self, deps, tools):
1533
- """ Hooked from ninja-cache plugin """
1534
-
1535
2273
  def _write_shell_file(self, basedir, deps, tools, writer):
1536
2274
  filepath = fs.path.join(basedir, "compile")
1537
2275
  with open(filepath, "w") as fobj:
@@ -1555,7 +2293,7 @@ def main():
1555
2293
  for object in objects:
1556
2294
  print(object)
1557
2295
  elif [arg for arg in sys.argv[1:] if arg == "-a"]:
1558
- subprocess.call(["ninja", "-v"])
2296
+ subprocess.call(["ninja", "-f", "{outdir}/build.ninja", "-v"])
1559
2297
  else:
1560
2298
  targets = []
1561
2299
  for arg in sys.argv[1:]:
@@ -1565,7 +2303,7 @@ def main():
1565
2303
  targets.extend(matches)
1566
2304
  if not targets:
1567
2305
  return
1568
- subprocess.call(["ninja", "-v"] + targets)
2306
+ subprocess.call(["ninja", "-f", "{outdir}/build.ninja", "-v"] + targets)
1569
2307
 
1570
2308
  if __name__ == "__main__":
1571
2309
  main()
@@ -1574,7 +2312,8 @@ if __name__ == "__main__":
1574
2312
  fobj.write(
1575
2313
  data.format(
1576
2314
  executable=sys.executable,
1577
- objects=[fs.path.relpath(o, self.outdir) for o in writer.objects]))
2315
+ objects=[fs.path.relpath(o, tools.wsroot) for o in writer.objects],
2316
+ outdir=self.outdir))
1578
2317
  tools.chmod(filepath, 0o777)
1579
2318
 
1580
2319
  def find_rule(self, ext):
@@ -1619,7 +2358,7 @@ if __name__ == "__main__":
1619
2358
  sources = [(tools.expand_path(source), origin) for source, origin in sources]
1620
2359
 
1621
2360
  # Aggregated list of sources for each rule
1622
- rule_source_list = {}
2361
+ rule_source_list = OrderedDict()
1623
2362
  while sources:
1624
2363
  source, origin = sources.pop()
1625
2364
  _, ext = fs.path.splitext(source)
@@ -1716,25 +2455,62 @@ if __name__ == "__main__":
1716
2455
  incrementally. The behavior can be changed with the ``incremental``
1717
2456
  class attribute.
1718
2457
  """
1719
-
1720
2458
  self.outdir = tools.builddir("ninja", self.incremental)
2459
+ self.outdir_rel = self.tools.expand_relpath(self.outdir, tools.wsroot)
1721
2460
  self._expand_headers()
1722
2461
  self._expand_sources(deps, tools)
1723
- self._prepare_ninja_cache(deps, tools)
1724
2462
  self._writer = self._write_ninja_file(self.outdir, deps, tools)
1725
- self._write_ninja_cache(deps, tools)
1726
2463
  verbose = " -v" if log.is_verbose() else ""
1727
2464
  threads = config.get("jolt", "threads", tools.getenv("JOLT_THREADS", None))
1728
2465
  threads = " -j" + threads if threads else ""
2466
+ keep_going = " -k 0" if config.get_keep_going() else ""
1729
2467
  depsfile = self._get_keepdepfile(tools)
1730
2468
  try:
1731
- tools.run("ninja{3}{2} -C {0} {1}", self.outdir, verbose, threads, depsfile)
2469
+ self.buildlog = tools.run(
2470
+ "ninja{3}{2}{5} -C {0} -f {4} {1}",
2471
+ tools.wsroot,
2472
+ verbose,
2473
+ threads,
2474
+ depsfile,
2475
+ fs.path.join(self.outdir, "build.ninja"),
2476
+ keep_going,
2477
+ output=True,
2478
+ )
2479
+ self._report_errors(self.buildlog)
1732
2480
  except JoltCommandError as e:
1733
- with utils.ignore_exception(), self.report() as report:
1734
- self._report_errors(report, "\n".join(e.stdout))
1735
- raise CompileError()
2481
+ self.buildlog = "\n".join(e.stdout)
2482
+ report = self._report_errors(self.buildlog)
2483
+ error = self._first_reported_error(report)
2484
+ if error:
2485
+ raise CompileError(error)
2486
+ raise e
2487
+
2488
+ if bool(getattr(self, "coverage", False)):
2489
+ self.covdatadir = tools.builddir("coverage-data")
2490
+ with tools.cwd(tools.wsroot):
2491
+ for obj in getattr(self._writer, "_objects", []):
2492
+ obj, ext = os.path.splitext(obj)
2493
+ obj = obj + ".gcno"
2494
+ try:
2495
+ tools.copy(obj, os.path.join(self.covdatadir, tools.expand_relpath(obj, "/")))
2496
+ except FileNotFoundError:
2497
+ pass
2498
+ if self.selfsustained:
2499
+ for _, artifact in deps.items():
2500
+ if artifact.paths.coverage_data:
2501
+ tools.copy(str(artifact.paths.coverage_data), self.covdatadir)
2502
+
2503
+ def publish(self, artifact, tools):
2504
+ if bool(getattr(self, "coverage", False)):
2505
+ self.publish_coverage_data(artifact, tools)
2506
+
2507
+ def publish_coverage_data(self, artifact, tools):
2508
+ """ Publishes code coverage data files. """
2509
+ with tools.cwd(self.covdatadir):
2510
+ if artifact.collect("**/*.gcno", "cov/"):
2511
+ artifact.paths.coverage_data = "cov"
1736
2512
 
1737
- def shell(self, deps, tools):
2513
+ def debugshell(self, deps, tools):
1738
2514
  """
1739
2515
  Invoked to start a debug shell.
1740
2516
 
@@ -1751,41 +2527,58 @@ if __name__ == "__main__":
1751
2527
  self._expand_headers()
1752
2528
  self._expand_sources(deps, tools)
1753
2529
  self.outdir = tools.builddir("ninja", self.incremental)
1754
- self._prepare_ninja_cache(deps, tools)
2530
+ self.outdir_rel = self.tools.expand_relpath(self.outdir, tools.wsroot)
1755
2531
  writer = self._write_ninja_file(self.outdir, deps, tools)
1756
- self._write_ninja_cache(deps, tools)
1757
2532
  self._write_shell_file(self.outdir, deps, tools, writer)
1758
2533
  pathenv = self.outdir + os.pathsep + tools.getenv("PATH")
1759
- with tools.cwd(self.outdir), tools.environ(PATH=pathenv):
2534
+ with tools.cwd(tools.wsroot), tools.environ(PATH=pathenv):
1760
2535
  print()
1761
2536
  print("Use the 'compile' command to build individual compilation targets")
1762
- super(CXXProject, self).shell(deps, tools)
1763
-
1764
- def _report_errors(self, report, logbuffer):
1765
- # GCC style errors
1766
- report.add_regex_errors_with_file(
1767
- "Compiler Error",
1768
- r"(?P<location>(?P<file>.*?):(?P<line>[0-9]+):(?P<col>[0-9]+)): (?P<message>.*)",
1769
- logbuffer,
1770
- self.outdir,
1771
- lambda err: not err["message"].startswith("note:"))
1772
-
1773
- # other compiler errors
1774
- report.add_regex_errors_with_file(
1775
- "Compiler Error",
1776
- r"(?P<location>(?P<file>.*?)\((?P<line>[0-9]+)\)): (?P<message>error: .*)",
1777
- logbuffer,
1778
- self.outdir)
1779
-
1780
- # Linker errors
1781
- report.add_regex_errors(
1782
- "Linker Error",
1783
- r"(?P<location>(?P<file>.*?):(.*?)): (?P<message>(undefined reference|multiple definition).*)",
1784
- logbuffer)
1785
- report.add_regex_errors(
1786
- "Linker Error",
1787
- r"(?P<location>ld): (error|warning): (?P<message>.*)",
1788
- logbuffer)
2537
+ super().debugshell(deps, tools)
2538
+
2539
+ def _report_errors(self, logbuffer):
2540
+ """ Parses the build log and reports errors. """
2541
+ with self.report() as report, utils.ignore_exception():
2542
+ # GCC style errors
2543
+ report.add_regex_errors_with_file(
2544
+ "Compiler Error",
2545
+ r"^(?P<location>(?P<file>.*?):(?P<line>[0-9]+):(?P<col>[0-9]+)): (?P<message>([^ ]*? )?(error:|[A-Z][0-9]*) .*)\n(?P<details>( ( |[0-9])*\| .*\n)+)?",
2546
+ logbuffer,
2547
+ self.outdir)
2548
+
2549
+ report.add_regex_errors_with_file(
2550
+ "Compiler Warning",
2551
+ r"^(?P<location>(?P<file>.*?):(?P<line>[0-9]+):(?P<col>[0-9]+)): (?P<message>([^ ]*? )?warning: .*)\n(?P<details>( ( |[0-9])*\| .*\n)+)?",
2552
+ logbuffer,
2553
+ self.outdir)
2554
+
2555
+ # MSVC compiler errors
2556
+ report.add_regex_errors_with_file(
2557
+ "Compiler Error",
2558
+ r"^(?P<location>(?P<file>.*?)\((?P<line>[0-9]+)\)): (?P<message>(fatal )?error( C[0-9]*?): .*)",
2559
+ logbuffer,
2560
+ self.outdir)
2561
+
2562
+ # Binutils/MSVC linker errors
2563
+ report.add_regex_errors(
2564
+ "Linker Error",
2565
+ r"^(?P<location>(?P<file>.*?)(:.*?)?)( )?: (?P<message>(( fatal)?error LNK|warning LNK|undefined reference|multiple definition).*)",
2566
+ logbuffer)
2567
+
2568
+ # LLVM linker errors
2569
+ report.add_regex_errors(
2570
+ "Linker Error",
2571
+ r"^(?P<location>(.*?)ld(\.lld)?): (error|warning): (?P<message>.*)",
2572
+ logbuffer)
2573
+
2574
+ return report
2575
+
2576
+ def _first_reported_error(self, report):
2577
+ """ Returns the first reported error or None if no errors were reported. """
2578
+ if report is None:
2579
+ return None
2580
+ for error in report.errors:
2581
+ return error
1789
2582
 
1790
2583
 
1791
2584
  class CXXLibrary(CXXProject):
@@ -1860,6 +2653,7 @@ class CXXLibrary(CXXProject):
1860
2653
  Include path metadata for this directory is automatically exported.
1861
2654
 
1862
2655
  """
2656
+ super().publish(artifact, tools)
1863
2657
 
1864
2658
  with tools.cwd(self.outdir):
1865
2659
  if not self.shared:
@@ -1945,6 +2739,7 @@ class CXXExecutable(CXXProject):
1945
2739
  the executable to allow consumers to run it easily.
1946
2740
 
1947
2741
  """
2742
+ super().publish(artifact, tools)
1948
2743
 
1949
2744
  with tools.cwd(self.outdir):
1950
2745
  if os.name == "nt":