jolt 0.9.355__py3-none-any.whl → 0.9.371__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.
jolt/plugins/linux.py ADDED
@@ -0,0 +1,943 @@
1
+
2
+ from jolt import Task, Parameter, ListParameter
3
+ from jolt import attributes
4
+ from jolt import log
5
+ from jolt import utils
6
+ from jolt.error import raise_task_error_if
7
+ from jolt.plugins import podman
8
+
9
+
10
+ import os
11
+ import shutil
12
+
13
+
14
+ def linux_arch_to_container_platform(arch):
15
+ """
16
+ Convert Linux architecture to Podman platform.
17
+
18
+ Args:
19
+ arch: Linux architecture.
20
+
21
+ Returns:
22
+ - linux/amd64
23
+ - linux/arm
24
+ - linux/arm64
25
+ - linux/mips
26
+ - linux/ppc64
27
+ - linux/riscv64
28
+ - linux/s390x
29
+ """
30
+ platforms = {
31
+ "arm": "linux/arm",
32
+ "arm64": "linux/arm64",
33
+ "mips": "linux/mips64le",
34
+ "powerpc": "linux/ppc64le",
35
+ "riscv": "linux/riscv64",
36
+ "s390": "linux/s390x",
37
+ "x86": "linux/amd64",
38
+ }
39
+ arch = str(arch)
40
+ try:
41
+ return platforms[arch]
42
+ except KeyError:
43
+ raise ValueError(f"Unsupported architecture {arch}")
44
+
45
+
46
+ def linux_arch_to_debian_arch(arch):
47
+ """
48
+ Convert Linux architecture to Debian architecture.
49
+
50
+ Args:
51
+ arch: Linux architecture.
52
+
53
+ Returns:
54
+ - amd64
55
+ - armhf
56
+ - arm64
57
+ - mips
58
+ - ppc64el
59
+ - riscv64
60
+ - s390x
61
+ """
62
+ debian_arch = {
63
+ "amd64": "amd64",
64
+ "arm": "armhf",
65
+ "arm64": "arm64",
66
+ "mips": "mips",
67
+ "powerpc": "ppc64el",
68
+ "riscv": "riscv64",
69
+ "s390": "s390x",
70
+ "x86": "i386",
71
+ }
72
+ return debian_arch[str(arch)]
73
+
74
+
75
+ class ArchParameter(Parameter):
76
+ """
77
+ Linux target architecture parameter.
78
+
79
+ Supported values:
80
+
81
+ - amd64
82
+ - arm
83
+ - arm64
84
+ - mips
85
+ - powerpc
86
+ - riscv
87
+ - s390
88
+ - x86
89
+
90
+ """
91
+
92
+ def __init__(self, *args, **kwargs):
93
+ kwargs["values"] = [
94
+ "amd64",
95
+ "arm",
96
+ "arm64",
97
+ "mips",
98
+ "powerpc",
99
+ "riscv",
100
+ "s390",
101
+ "x86",
102
+ ]
103
+ kwargs["help"] = "Target architecture."
104
+ super().__init__(*args, **kwargs)
105
+
106
+
107
+ class _ContainerImageBase(podman.ContainerImage):
108
+ """ Builds a container image from external source tree """
109
+
110
+ abstract = True
111
+ """ Must be subclassed """
112
+
113
+ arch = ArchParameter()
114
+ """ Target architecture [amd64, arm, arm64, mips, powerpc, riscv, s390, x86] """
115
+
116
+ @property
117
+ def target(self):
118
+ return linux_arch_to_container_platform(self.arch)
119
+
120
+
121
+ class _DebianSdkImage(podman.ContainerImage):
122
+ """ Provides a Debian SDK for building Linux kernel and U-Boot """
123
+
124
+ abstract = True
125
+ """ Must be subclassed """
126
+
127
+ arch = ArchParameter()
128
+ """ Target architecture """
129
+
130
+ dockerfile = """
131
+ FROM debian:{version}-slim
132
+ ARG DEBIAN_FRONTEND=noninteractive
133
+ RUN apt-get update && apt-get install -y crossbuild-essential-{debian_arch}
134
+ """
135
+
136
+ version = "stable"
137
+ """ Debian codename/version """
138
+
139
+ @property
140
+ def debian_arch(self):
141
+ return {
142
+ "amd64": "amd64",
143
+ "arm": "armhf",
144
+ "arm64": "arm64",
145
+ "mips": "mips",
146
+ "powerpc": "ppc64el",
147
+ "riscv": "riscv64",
148
+ "s390": "s390x",
149
+ "x86": "i386",
150
+ }[str(self.arch)]
151
+
152
+
153
+ class DebianHostSdk(Task):
154
+ """
155
+ Helper task that exports a Debian host's cross-compiler toolchains to consumers.
156
+
157
+ The task verifies that the cross-compiler toolchains are installed on the host.
158
+ If not, the task will raise an error with instructions.
159
+
160
+ Exported environment variables:
161
+
162
+ - ``CC``: Cross-compiler
163
+ - ``CPP``: Cross-preprocessor
164
+ - ``CXX``: Cross-C++ compiler
165
+ - ``CROSS_COMPILE``: Cross-compile prefix
166
+
167
+ """
168
+ abstract = True
169
+ """ Must be subclassed """
170
+
171
+ arch = ArchParameter()
172
+ """ Target architecture [amd64, arm, arm64, mips, powerpc, riscv, s390, x86] """
173
+
174
+ def publish(self, artifact, tools):
175
+ arch_to_cross_compile = {
176
+ "amd64": "x86_64-linux-gnu-",
177
+ "arm": "arm-linux-gnueabihf-",
178
+ "arm64": "aarch64-linux-gnu-",
179
+ "mips": "mips-linux-gnu-",
180
+ "powerpc": "powerpc64-linux-gnu-",
181
+ "riscv": "riscv64-linux-gnu-",
182
+ "s390": "s390x-linux-gnu-",
183
+ "x86": "i686-linux-gnu-",
184
+ }
185
+ debian_arch = linux_arch_to_debian_arch(self.arch)
186
+ debian_cross = arch_to_cross_compile[str(self.arch)]
187
+ raise_task_error_if(
188
+ not tools.which(arch_to_cross_compile[str(self.arch)] + "gcc"),
189
+ self, f"Cross compiler not found. Please install crossbuild-essential-{debian_arch} package.")
190
+ artifact.environ.CC = f"{debian_cross}gcc"
191
+ artifact.environ.CPP = f"{debian_cross}cpp"
192
+ artifact.environ.CXX = f"{debian_cross}g++"
193
+ artifact.environ.CROSS_COMPILE = debian_cross
194
+
195
+
196
+ class Initramfs(_ContainerImageBase):
197
+ """
198
+ Builds an initramfs image using Podman.
199
+
200
+ The task builds the initramfs image using the given Dockerfile and
201
+ publishes the resulting cpio archive. The task requires the following
202
+ attributes to be set:
203
+
204
+ - arch: Target architecture
205
+ - dockerfile: Path to Dockerfile, or Dockerfile content.
206
+
207
+ When building images for an architecture other than the host, the
208
+ binfmt-support package must be installed and configured to support the
209
+ target architecture. The package is available in most Linux distributions.
210
+
211
+ The location of the resulting cpio archive is stored in the
212
+ 'artifact.paths.cpio' attribute.
213
+
214
+ """
215
+ abstract = True
216
+ """ Must be subclassed """
217
+
218
+ output = ["cpio"]
219
+
220
+ def publish(self, artifact, tools):
221
+ super().publish(artifact, tools)
222
+ artifact.strings.arch = str(self.arch)
223
+ artifact.paths.cpio = "cpio/{_imagefile}.cpio"
224
+
225
+
226
+ class Squashfs(_ContainerImageBase):
227
+ """
228
+ Builds a squashfs image using Podman.
229
+
230
+ The task builds a container image using the given Dockerfile and converts
231
+ the resulting container filesystem to a squashfs image which is published.
232
+
233
+ When building images for an architecture other than the host, the
234
+ binfmt-support package must be installed and configured to support running
235
+ applications for the target architecture. The package is available in most
236
+ Linux distributions.
237
+
238
+ The location of the resulting squashfs image is stored in the
239
+ ``artifact.paths.squashfs`` artifact attribute.
240
+ """
241
+ abstract = True
242
+ """ Must be subclassed """
243
+
244
+ output = ["squashfs"]
245
+
246
+ size = None
247
+ """
248
+ Size of the squashfs image.
249
+
250
+ Typically used to align the image size to a supported SD card size (power of two).
251
+
252
+ Supported units are 'K', 'M', 'G', 'T'.
253
+ """
254
+
255
+ def run(self, deps, tools):
256
+ super().run(deps, tools)
257
+ if self.size:
258
+ with tools.cwd(tools.builddir("squashfs")):
259
+ tools.run("fallocate -l {size} image.squashfs")
260
+
261
+ def publish(self, artifact, tools):
262
+ super().publish(artifact, tools)
263
+ artifact.strings.arch = str(self.arch)
264
+ artifact.paths.squashfs = "squashfs/{_imagefile}.squashfs"
265
+
266
+
267
+ class _KernelBase(Task):
268
+ abstract = True
269
+ """ Must be subclassed """
270
+
271
+ arch = ArchParameter()
272
+ """ Linux target architecture [amd64, arm, arm64, mips, powerpc, riscv, s390, x86] """
273
+
274
+ defconfig = Parameter("allnoconfig", help="Name of kernel defconfig")
275
+ """ Default configuration """
276
+
277
+ features = ListParameter([], help="List of feature configs to apply")
278
+ """
279
+ List of feature configuration snippets to apply.
280
+
281
+ If used, the task will search for feature configuration snippets in the
282
+ configured paths and merge them into the selected defconfig.
283
+ """
284
+
285
+ configpaths = [
286
+ "{srcdir}/arch/{arch}/configs",
287
+ ]
288
+ """
289
+ Paths to configuration file snippets.
290
+
291
+ When building the kernel with features, the task will search for
292
+ feature configuration snippets in these directories. The snippets
293
+ are applied and merged with the selected defconfig.
294
+ """
295
+
296
+ defconfigpath = "arch/{arch}/configs/{defconfig}_defconfig"
297
+ """ Path to defconfig file. """
298
+
299
+ srcdir = None
300
+ """ Location of Linux kernel source tree """
301
+
302
+ targets = ListParameter(help="Targets to build and publish")
303
+ """ Override and set accepted values """
304
+
305
+ def clean(self, tools):
306
+ self.moddir = tools.builddir("modules")
307
+ self.objdir = tools.builddir("objects", incremental=True)
308
+ tools.rmtree(self.moddir)
309
+ tools.rmtree(self.objdir)
310
+
311
+ def run(self, deps, tools):
312
+ raise_task_error_if(not tools.which("bison"), self, "bison is required to build the kernel")
313
+ raise_task_error_if(not tools.which("flex"), self, "flex is required to build the kernel")
314
+ raise_task_error_if(not tools.which("make"), self, "make is required to build the kernel")
315
+
316
+ self.objdir = tools.builddir("objects", incremental=True)
317
+
318
+ config = tools.expand_path("{objdir}/.config")
319
+ tools.unlink(config, ignore_errors=True)
320
+
321
+ arch = f"ARCH={self.arch}" if self.arch else ""
322
+ verbose = "V=1" if log.is_verbose() else ""
323
+ defconf_conf = self.run_find_defconfig_configs(deps, tools)
324
+ feature_confs = self.run_find_feature_configs(deps, tools)
325
+ defconfigpath = tools.expand(self.defconfigpath)
326
+ if self.defconfig in ["defconfig"]:
327
+ defconfigpath = os.path.join(os.path.dirname(defconfigpath), "defconfig")
328
+
329
+ with tools.cwd(self.srcdir):
330
+ if self.defconfig in ["allnoconfig"]:
331
+ self.info("Generating allnoconfig configuration")
332
+ tools.run("{} make allnoconfig O={objdir}", arch)
333
+ tools.run("{} scripts/kconfig/merge_config.sh -O {objdir} {objdir}/.config {} {}", arch, defconf_conf, " ".join(feature_confs))
334
+ else:
335
+ self.info("Generating {defconfig} configuration")
336
+ tools.run("{} scripts/kconfig/merge_config.sh -O {objdir} {} {} {}", arch, defconfigpath, defconf_conf, " ".join(feature_confs))
337
+
338
+ # Run build for each requested target
339
+ for target in self.targets:
340
+ fn = getattr(self, f"run_{target}", None)
341
+ assert fn, f"Dont know how to build {target}"
342
+ with tools.runprefix("{} make {} O={objdir} -j{}", arch, verbose, tools.thread_count()):
343
+ fn(deps, tools)
344
+
345
+ def run_find_defconfig_configs(self, deps, tools):
346
+ """ Finds local overrides for the chosen defconfig """
347
+ configpaths = [tools.expand_path(tools.expand(path)) for path in getattr(self, "configpaths", [])]
348
+ return shutil.which(tools.expand("{defconfig}.config"), os.F_OK, os.pathsep.join(configpaths)) or ""
349
+
350
+ def run_find_feature_configs(self, deps, tools):
351
+ """ Finds local overrides for chosen features """
352
+ configpaths = [tools.expand_path(tools.expand(path)) for path in getattr(self, "configpaths", [])]
353
+ return [shutil.which(feature + ".config", os.F_OK, os.pathsep.join(configpaths)) or "" for feature in self.features]
354
+
355
+ def publish(self, artifact, tools):
356
+ artifact.strings.arch = str(self.arch)
357
+
358
+ for target in self.targets:
359
+ fn = getattr(self, f"publish_{target}", None)
360
+ assert fn, f"Dont know how to publish {target}"
361
+ fn(artifact, tools)
362
+
363
+
364
+ class UBoot(_KernelBase):
365
+ """
366
+ Builds u-boot makefile target(s) and publishes the result.
367
+
368
+ An implementor must subclass this task and set the following attributes:
369
+
370
+ - srcdir: Path to U-boot source tree
371
+
372
+ It is recommended to use a task dependency to setup the
373
+ required tools by adding them to ``PATH`` and/or setting the ``CROSS_COMPILE``
374
+ environment variable. When building on a Debian host, the
375
+ :class:`jolt.plugins.linux.DebianHostSdk` helper task can be used to export
376
+ the cross-compiler tools.
377
+
378
+ """
379
+ abstract = True
380
+
381
+ defconfig = Parameter("allnoconfig", help="Name of u-boot defconfig")
382
+ """ Default configuration """
383
+
384
+ targets = ListParameter(
385
+ ["uboot"],
386
+ values=["tools", "uboot"],
387
+ help="Targets to build and publish",
388
+ )
389
+ """ Build targets [tools, uboot] """
390
+
391
+ def run_tools(self, deps, tools):
392
+ self.info("Building tools ...")
393
+ tools.run("tools")
394
+
395
+ def run_uboot(self, deps, tools):
396
+ self.info("Building u-boot ...")
397
+ tools.run("u-boot.bin")
398
+
399
+ def publish_tools(self, artifact, tools):
400
+ self.info("Publishing tools ...")
401
+ with tools.cwd(self.objdir):
402
+ artifact.collect("tools/mkimage")
403
+ artifact.environ.PATH.append("tools")
404
+
405
+ def publish_uboot(self, artifact, tools):
406
+ self.info("Publishing u-boot ...")
407
+ with tools.cwd(self.objdir):
408
+ artifact.collect("*.bin")
409
+
410
+
411
+ @utils.concat_attributes("dtbs", "dtbs_{defconfig}")
412
+ class Kernel(_KernelBase):
413
+ """
414
+ Builds kernel makefile target(s) and publishes the result.
415
+
416
+ Targets are selected by assigning the ``targets`` parameter. The following
417
+ values are supported:
418
+
419
+ - ``binary``: Build kernel binary
420
+ - ``dtbs``: Build device trees
421
+ - ``dtc``: Build device tree compiler
422
+ - ``gzimage``: Build gzipped kernel image
423
+ - ``image``: Build kernel image
424
+ - ``modules``: Build kernel modules
425
+ - ``uimage``: Build uImage
426
+ - ``vmlinux``: Build vmlinux
427
+ - ``zimage``: Build zImage
428
+
429
+ Mutliple targets can be selected at once, such as ``vmlinux+dtbs+modules``.
430
+ The resulting artifacts are published into different directories
431
+ based on the target name.
432
+
433
+ It is recommended to use a task dependency to setup the
434
+ required tools by adding them to ``PATH`` and/or setting the ``CROSS_COMPILE``
435
+ environment variable. When building on a Debian host, the
436
+ :class:`jolt.plugins.linux.DebianHostSdk` helper task can be used to export
437
+ the cross-compiler tools.
438
+
439
+ The following tools are required to be available in PATH:
440
+
441
+ - bison
442
+ - flex
443
+ - make
444
+ - gcc
445
+
446
+ Artifact path attributes are created to point to the published files.
447
+
448
+ """
449
+
450
+ abstract = True
451
+ """ Must be subclassed """
452
+
453
+ dtbs = []
454
+ """
455
+ List of device trees to build when the 'dtbs' target is requested.
456
+
457
+ If empty, all device trees associated with the target architecture are built.
458
+ """
459
+
460
+ loadaddr = 0
461
+ """
462
+ Kernel load address.
463
+ """
464
+
465
+ loadaddr_fdt = 0
466
+ """
467
+ Device-tree load address.
468
+ """
469
+
470
+ targets = ListParameter(
471
+ ["vmlinux"],
472
+ values=["binary", "dtbs", "dtc", "gzimage", "image", "modules", "uimage", "vmlinux", "zimage"],
473
+ help="Targets to build and publish",
474
+ )
475
+ """ Build targets [binary, dtbs, dtc, gzimage, image, modules, uimage, vmlinux, zimage] """
476
+
477
+ def run_binary(self, deps, tools):
478
+ self.info("Building binary kernel ...")
479
+ tools.run("LOADADDR={loadaddr} vmlinux")
480
+ objcopy = tools.getenv("OBJCOPY", tools.getenv("CROSS_COMPILE", "") + "objcopy")
481
+ tools._run_prefix = [] # Hack to cancel previous prefix
482
+ tools.run("{} -O binary -R .note -R .comment -S {objdir}/vmlinux {objdir}/vmlinux.bin", objcopy)
483
+
484
+ def run_dtbs(self, deps, tools):
485
+ self.info("Building device trees ...")
486
+ if not self._dtbs():
487
+ tools.run("dtbs")
488
+ else:
489
+ tools.run(" ".join(self._dtbs()))
490
+
491
+ def run_dtc(self, deps, tools):
492
+ self.info("Building device tree compiler ...")
493
+ tools.run("CONFIG_DTC=y scripts")
494
+
495
+ def run_modules(self, deps, tools):
496
+ self.info("Building modules ...")
497
+ self.moddir = tools.builddir("modules")
498
+ tools.run("INSTALL_MOD_PATH={moddir} modules")
499
+ tools.run("INSTALL_MOD_PATH={moddir} modules_install")
500
+
501
+ def run_image(self, deps, tools):
502
+ self.info("Building Image ...")
503
+ tools.run("LOADADDR={loadaddr} Image")
504
+
505
+ def run_gzimage(self, deps, tools):
506
+ self.info("Building Image.gz ...")
507
+ tools.run("LOADADDR={loadaddr} Image.gz")
508
+
509
+ def run_uimage(self, deps, tools):
510
+ self.info("Building uImage ...")
511
+ tools.run("LOADADDR={loadaddr} uImage")
512
+
513
+ def run_vmlinux(self, deps, tools):
514
+ self.info("Building vmlinux ...")
515
+ tools.run("vmlinux")
516
+
517
+ def run_zimage(self, deps, tools):
518
+ self.info("Building zImage ...")
519
+ tools.run("zImage")
520
+
521
+ def publish(self, artifact, tools):
522
+ super().publish(artifact, tools)
523
+
524
+ if self.loadaddr != 0:
525
+ artifact.strings.loadaddr = str(self.loadaddr)
526
+ if self.loadaddr_fdt != 0:
527
+ artifact.strings.loadaddr_fdt = str(self.loadaddr_fdt)
528
+
529
+ def publish_binary(self, artifact, tools):
530
+ self.info("Publishing binary ...")
531
+ with tools.cwd(self.objdir):
532
+ artifact.collect("vmlinux.bin", "binary/")
533
+ artifact.paths.binary = "binary/vmlinux.bin"
534
+
535
+ def publish_dtbs(self, artifact, tools):
536
+ self.info("Publishing device trees ...")
537
+ with tools.cwd(self.objdir, "arch/{arch}/boot/dts"):
538
+ if not self._dtbs():
539
+ artifact.collect("**/*.dtb", "dtbs/")
540
+ else:
541
+ for dtb in self._dtbs():
542
+ artifact.collect(dtb, "dtbs/")
543
+ artifact.paths.dtbs = "dtbs"
544
+
545
+ def publish_dtc(self, artifact, tools):
546
+ self.info("Publishing device tree compiler ...")
547
+ with tools.cwd(self.objdir, "scripts/dtc"):
548
+ artifact.collect("dtc", "bin/")
549
+ artifact.environ.PATH.append("bin")
550
+
551
+ def publish_modules(self, artifact, tools):
552
+ self.info("Publishing modules ...")
553
+ with tools.cwd(self.moddir):
554
+ artifact.collect(".", "modules/", symlinks=True)
555
+ artifact.paths.modules = "modules"
556
+
557
+ with tools.cwd(self.objdir):
558
+ if os.path.exists(tools.expand_path("Module.symvers")):
559
+ artifact.collect("Module.symvers")
560
+ artifact.paths.symvers = "Module.symvers"
561
+
562
+ def publish_image(self, artifact, tools):
563
+ self.info("Publishing Image ...")
564
+ with tools.cwd(self.objdir, "arch/{arch}/boot"):
565
+ artifact.collect("Image", "image/")
566
+ artifact.paths.image = "image/Image"
567
+
568
+ def publish_gzimage(self, artifact, tools):
569
+ self.info("Publishing Image.gz ...")
570
+ with tools.cwd(self.objdir, "arch/{arch}/boot"):
571
+ artifact.collect("Image.gz", "gzimage/")
572
+ artifact.paths.gzimage = "gzimage/Image.gz"
573
+
574
+ def publish_uimage(self, artifact, tools):
575
+ self.info("Publishing uImage ...")
576
+ with tools.cwd(self.objdir, "arch/{arch}/boot"):
577
+ artifact.collect("uImage", "uimage/")
578
+ artifact.paths.uimage = "uimage/uImage"
579
+
580
+ def publish_vmlinux(self, artifact, tools):
581
+ self.info("Publishing vmlinux ...")
582
+ with tools.cwd(self.objdir):
583
+ artifact.collect("vmlinux", "vmlinux/")
584
+ artifact.paths.vmlinux = "vmlinux/vmlinux"
585
+
586
+ def publish_zimage(self, artifact, tools):
587
+ self.info("Publishing zImage ...")
588
+ with tools.cwd(self.objdir, "arch/{arch}/boot"):
589
+ artifact.collect("zImage", "zimage/")
590
+ artifact.paths.zimage = "zimage/zImage"
591
+
592
+
593
+ class Module(Kernel):
594
+ """
595
+ Builds a kernel module from external source tree.
596
+
597
+ The task is based on the :class:`Kernel` task and builds a kernel module
598
+ from the given source tree specified by the ``srcdir_module`` attribute.
599
+ The ``targets`` attribute is set to ``modules`` by default.
600
+
601
+ See :class:`Kernel` for additional information on building kernel targets.
602
+ In particular, the ``srcdir`` attribute must be set to the kernel source tree
603
+ to build the modules against.
604
+ """
605
+
606
+ abstract = True
607
+ """ Must be subclassed """
608
+
609
+ srcdir_module = None
610
+ """ Path to kernel module source tree """
611
+
612
+ targets = ["modules"]
613
+ """ Build targets [modules] """
614
+
615
+ def run(self, deps, tools):
616
+ self.targets = ["modules"]
617
+ super().run(deps, tools)
618
+
619
+ def run_modules(self, deps, tools):
620
+ with tools.cwd(self.objdir):
621
+ for _, artifact in deps.items():
622
+ if str(artifact.paths.symvers):
623
+ self.info("Copying Module.symvers")
624
+ tools.copy(str(artifact.paths.symvers), ".")
625
+
626
+ self.info("Building modules ...")
627
+ self.moddir = tools.builddir("modules")
628
+ tools.run("INSTALL_MOD_PATH={moddir} modules_prepare")
629
+ tools.run("INSTALL_MOD_PATH={moddir} M={joltdir}/{srcdir_module} modules")
630
+ tools.run("INSTALL_MOD_PATH={moddir} M={joltdir}/{srcdir_module} modules_install")
631
+
632
+
633
+ @utils.concat_attributes("configs", "configs")
634
+ class FIT(Task):
635
+ """
636
+ Builds and publishes a FIT Image.
637
+
638
+ The task builds a FIT image using the given kernel, device tree and ramdisk
639
+ tasks and publishes the resulting image. Multiple FIT configurations can be
640
+ created by defining the ``configs`` attribute. The image is optinally signed
641
+ using the provided key.
642
+
643
+ The task requires the ``mkimage`` tool to be available in PATH.
644
+ """
645
+
646
+ abstract = True
647
+ """ Must be subclassed """
648
+
649
+ configs = {}
650
+ """
651
+ FIT configurations to create.
652
+
653
+ Dictionary of 2 or 3 element tuples according to this format:
654
+
655
+ .. code-block:: python
656
+
657
+ configs = {
658
+ "<name of config>": ("<name of kernel task>", "<name of dtb>"),
659
+ "<name of config with ramdisk>": ("<name of kernel task>", "<name of dtb>", "<name of ramdisk task>"),
660
+ }
661
+
662
+ Example:
663
+
664
+ .. code-block:: python
665
+
666
+ requires = [
667
+ "kernel=linux/kernel:arch=arm,targets=dtbs+binary_gz,defconfig=bcm2835",
668
+ "ramdisk=busybox/irfs:arch=armv7"
669
+ ]
670
+
671
+ configs = {
672
+ "conf-rpi0w": ("kernel", "bcm2835-rpi-zero-w.dtb"),
673
+ "conf-rpi0w-irfs": ("kernel", "bcm2835-rpi-zero-w.dtb", "ramdisk"),
674
+ }
675
+
676
+ Loaded from defs/{defconfig}.py
677
+ """
678
+
679
+ config_default = None
680
+ """ Default FIT configuration """
681
+
682
+ _template = """/dts-v1/;
683
+
684
+ / {
685
+ description = "U-Boot fitImage";
686
+
687
+ images {
688
+ {% for i, kernel in enumerate(_kernels) %}
689
+ kernel@{{kernel}} {
690
+ description = "Linux kernel";
691
+ {% if str(deps[kernel].paths.zimage) %}
692
+ data = /incbin/("{{deps[kernel].paths.zimage}}");
693
+ compression = "none";
694
+ {% elif str(deps[kernel].paths.gzimage) %}
695
+ data = /incbin/("{{deps[kernel].paths.gzimage}}");
696
+ compression = "gzip";
697
+ {% elif str(deps[kernel].paths.image) %}
698
+ data = /incbin/("{{deps[kernel].paths.image}}");
699
+ compression = "none";
700
+ {% else %}
701
+ #error "Kernel type not supported."
702
+ {% endif %}
703
+ type = "kernel";
704
+ arch = "{{deps[kernel].strings.arch}}";
705
+ os = "linux";
706
+ {% if deps[kernel].strings.loadaddr %}
707
+ load = <{{deps[kernel].strings.loadaddr}}>;
708
+ entry = <{{deps[kernel].strings.loadaddr}}>;
709
+ {% endif %}
710
+ hash {
711
+ algo = "sha256";
712
+ };
713
+ };
714
+
715
+ {% endfor %}
716
+ {% for kernel, dtb in _dtbs %}
717
+ fdt@{{path.basename(dtb)}} {
718
+ description = "Flattened Device Tree blob";
719
+ data = /incbin/("{{deps[kernel].paths.dtbs}}/{{dtb}}");
720
+ type = "flat_dt";
721
+ arch = "{{deps[kernel].strings.arch}}";
722
+ compression = "none";
723
+ os = "linux";
724
+ {% if deps[kernel].strings.loadaddr_fdt %}
725
+ load = <{{deps[kernel].strings.loadaddr_fdt}}>;
726
+ {% endif %}
727
+ hash {
728
+ algo = "sha256";
729
+ };
730
+ };
731
+
732
+ {% endfor %}
733
+ {% for ramdisk in _ramdisks %}
734
+ ramdisk@{{ramdisk}} {
735
+ description = "Ramdisk";
736
+ data = /incbin/("{{deps[ramdisk].paths.cpio}}");
737
+ type = "ramdisk";
738
+ arch = "{{deps[ramdisk].strings.arch}}";
739
+ os = "linux";
740
+ {% if deps[ramdisk].strings.compression %}
741
+ compression = "{{deps[ramdisk].strings.compression}}";
742
+ {% else %}
743
+ compression = "none";
744
+ {% endif %}
745
+ hash {
746
+ algo = "sha256";
747
+ };
748
+ };
749
+
750
+ {% endfor %}
751
+ };
752
+
753
+ configurations {
754
+ {% if config_default %}
755
+ default = "conf@{{config_default}}";
756
+ {% endif %}
757
+
758
+ {% for name, config in _configs().items() %}
759
+ conf@{{name}} {
760
+ {% if len(config) > 2 %}
761
+ description = "Linux kernel, FDT blob, Ramdisk";
762
+ {% else %}
763
+ description = "Linux kernel, FDT blob";
764
+ {% endif %}
765
+ kernel = "kernel@{{config[0]}}";
766
+ os = "linux";
767
+ fdt = "fdt@{{path.basename(config[1])}}";
768
+ {% if len(config) > 2 %}
769
+ ramdisk = "ramdisk@{{config[2]}}";
770
+ {% endif %}
771
+
772
+ hash {
773
+ algo = "sha256";
774
+ };
775
+ signature {
776
+ algo = "sha256,rsa2048";
777
+ {% if signature_key_name %}
778
+ key-name-hint = "{{signature_key_name}}";
779
+ {% endif %}
780
+ {% if len(config) > 2 %}
781
+ sign-images = "kernel", "fdt", "ramdisk";
782
+ {% else %}
783
+ sign-images = "kernel", "fdt";
784
+ {% endif %}
785
+ };
786
+ };
787
+
788
+ {% endfor %}
789
+ };
790
+ };
791
+ """
792
+
793
+ signature_key_name = None
794
+ """ Name of key used to sign image. If None, image won't be signed. """
795
+
796
+ signature_key_path = None
797
+ """ Directory path to signature key """
798
+
799
+ def run(self, deps, tools):
800
+ assert tools.which("mkimage"), "mkimage is required to build the FIT image"
801
+
802
+ kernels = []
803
+ dtbs = []
804
+ ramdisks = []
805
+
806
+ # Add kernels and dtbs to individual lists
807
+ for name, config in self._configs().items():
808
+ kernels.append(config[0])
809
+ dtbs.append((config[0], config[1]))
810
+ try:
811
+ ramdisks.append(config[2])
812
+ except IndexError:
813
+ pass
814
+ dtbs = list(set(dtbs))
815
+ kernels = list(set(kernels))
816
+
817
+ # Render image device tree source template
818
+ self.info("Rendering FIT image source file")
819
+ its = tools.render(
820
+ self._template,
821
+ enumerate=enumerate,
822
+ len=len,
823
+ path=os.path,
824
+ print=print,
825
+ str=str,
826
+ deps=deps,
827
+ _dtbs=list(set(dtbs)),
828
+ _kernels=list(set(kernels)),
829
+ _ramdisks=list(set(ramdisks)))
830
+
831
+ with tools.cwd(tools.builddir()):
832
+ tools.write_file("fitImage.its", its, expand=False)
833
+ print(its)
834
+
835
+ self.info("Building FIT image")
836
+ tools.run(["mkimage", "-V"], shell=False)
837
+ tools.run(["mkimage", "-D", "-I dts -O dtb -p 2000", "-f", "fitImage.its", "fitImage"], shell=False)
838
+
839
+ # Optionally sign the image
840
+ if self.signature_key_name:
841
+ self.info("Signing FIT image")
842
+ signature_key_path = tools.expand_path(self.signature_key_path)
843
+ tools.copy("fitImage", "fitImage.unsigned")
844
+ tools.run(["mkimage", "-D", "-I dts -O dtb -p 2000", "-F", "-k", signature_key_path, "-r", "fitImage"], shell=False)
845
+
846
+ def publish(self, artifact, tools):
847
+ with tools.cwd(tools.builddir()):
848
+ artifact.collect("fitImage")
849
+ artifact.collect("fitImage.unsigned")
850
+ artifact.collect("fitImage.its")
851
+ artifact.paths.fitimage = "fitImage"
852
+
853
+
854
+ @attributes.attribute("binary", "binary_{arch}")
855
+ class Qemu(Task):
856
+ """
857
+ Runs Qemu with the given kernel, initramfs and rootfs.
858
+
859
+ The task automatically selects the correct Qemu binary for the architecture
860
+ and builds the command line arguments based on the provided kernel, initramfs
861
+ and rootfs tasks.
862
+
863
+ Additional arguments can be passed to QEMU by setting the ``arguments``
864
+ attribute.
865
+
866
+ The selected Qemu binary must be available in PATH.
867
+ """
868
+
869
+ abstract = True
870
+ """ Must be subclassed """
871
+
872
+ arch = ArchParameter()
873
+ """ Target architecture [amd64, arm, arm64, mips, powerpc, riscv, s390, x86] """
874
+
875
+ arguments = []
876
+ """ Additional arguments to pass to QEMU """
877
+
878
+ binary = None
879
+ """ Name of QEMU binary """
880
+
881
+ binary_arm = "qemu-system-arm"
882
+ binary_arm64 = "qemu-system-aarch64"
883
+ binary_mips = "qemu-system-mips"
884
+ binary_powerpc = "qemu-system-ppc"
885
+ binary_riscv = "qemu-system-riscv64"
886
+ binary_s390 = "qemu-system-s390x"
887
+ binary_x86 = "qemu-system-x86_64"
888
+
889
+ cacheable = False
890
+ """ Task is not cacheable """
891
+
892
+ dtb = None
893
+ """ Path to device tree in kernel artifact """
894
+
895
+ initrd = None
896
+ """ Name of initrd/ramfs task, if any """
897
+
898
+ kernel = None
899
+ """ Name of kernel task """
900
+
901
+ machine = None
902
+ """ Target machine """
903
+
904
+ memory = None
905
+ """ Memory size. Example: 512M """
906
+
907
+ rootfs = None
908
+ """ Name of rootfs task, if any """
909
+
910
+ def requires(self):
911
+ r = []
912
+ if self.kernel:
913
+ r.append("kernel=" + self.kernel)
914
+ if self.initrd:
915
+ r.append("initrd=" + self.initrd)
916
+ if self.rootfs:
917
+ r.append("rootfs=" + self.rootfs)
918
+ return r
919
+
920
+ def run(self, deps, tools):
921
+ assert tools.which(self.binary), "{binary} is required to run QEMU"
922
+ assert self.machine, "Machine type is required to run QEMU"
923
+
924
+ self.deps = deps
925
+
926
+ # Get kernel and initrd paths
927
+ arguments = self.expand(self.arguments)
928
+ dtb = ["-dtb", os.path.join(str(deps[self.kernel].paths.dtbs), self.dtb)] if self.dtb else []
929
+ initrd = ["-initrd", str(deps[self.initrd].paths.cpio)] if self.initrd else []
930
+ kernel = ["-kernel", str(deps[self.kernel].paths.zimage)] if self.kernel else []
931
+ memory = ["-m", self.memory] if self.memory else []
932
+
933
+ # Run QEMU
934
+ binary = tools.which(self.binary)
935
+
936
+ cmdline = [
937
+ binary,
938
+ "-M", self.machine,
939
+ ] + kernel + dtb + initrd + memory + arguments
940
+
941
+ self.info("Running QEMU with command: {}", cmdline)
942
+
943
+ os.execv(binary, cmdline)