-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
1047 lines (984 loc) · 60.2 KB
/
Copy pathMakefile
File metadata and controls
1047 lines (984 loc) · 60.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
################################################################################
#
# Top-level wrapper for the MISTER BR2_EXTERNAL tree (P1.1, PLAN.md §6).
#
# Buildroot itself is NEVER vendored into this repository (G4/G6, TASKS.md
# standing rule 1 — "No binaries in git. Ever."). This Makefile:
#
# 1. Downloads the pinned upstream Buildroot release tarball into an
# untracked cache directory (dl/).
# 2. Verifies its SHA-256 against the pinned hash below and aborts loudly
# on any mismatch — it will NOT unpack an unverified tarball.
# 3. Unpacks it to work/buildroot/ (idempotent: if a Buildroot tree of the
# pinned version is already present there, the download/verify/unpack
# step is skipped entirely and no network access is made).
# 4. Forwards every other target (menuconfig, mister_de10nano_defconfig,
# olddefconfig, savedefconfig, ...) into that Buildroot tree with
# BR2_EXTERNAL set to this repo, O= pointed at an out-of-tree output
# directory, and BR2_DL_DIR pointed at the persistent download cache.
#
# work/, dl/, and output/ are all gitignored — see .gitignore.
#
# Reference: /mnt/source/sb-enema/Makefile (working 2026.02.3 pinned-tarball
# wrapper this is modeled on). That reference has no hash verification; this
# Makefile adds it, which is the whole point of P1.1.
#
################################################################################
# --- Buildroot pin ------------------------------------------------------------
# P4.6 wires a Renovate custom regex manager over BUILDROOT_VERSION /
# BUILDROOT_SHA256 in this file (PLAN.md §9), the same way sb-enema's
# renovate.json does for BUILDROOT_VERSION — keep this stanza regex-friendly.
#
# WHERE BUILDROOT_SHA256 COMES FROM — read before changing it.
# The hash below is transcribed from Buildroot's GPG-clearsigned release
# manifest for this exact version:
#
# https://buildroot.org/downloads/buildroot-$(BUILDROOT_VERSION).tar.gz.sign
#
# which contains a "SHA256: <hash> buildroot-<version>.tar.gz" line signed by
# the Buildroot maintainer. That signed file is the ONLY source of truth for
# this value.
#
# Do NOT produce this hash by downloading the tarball and running sha256sum on
# it. That is circular — it pins whatever bytes you happened to receive, and
# certifies nothing. A bump (manual or Renovate) MUST take the new hash from
# the .sign file for the new version. `make buildroot-showsig` prints it.
#
# Since 2026-08-24 a Renovate bump gets this transcription done FOR it:
# .github/workflows/renovate-hash-sync.yml case 6
# (scripts/hash-sync-buildroot.sh) fetches the same .sign manifest and
# rewrites the hash below on the PR branch. That script transcribes the
# signed manifest exactly as a human would — it never downloads the tarball,
# so the prohibition above stands untouched. The manual `make
# buildroot-showsig` transcription remains the procedure for a hand bump, and
# the fallback whenever the sync run skipped (the PR then stays red at `make
# buildroot-verify`, which is the safe failure mode).
BUILDROOT_VERSION ?= 2026.05.2
BUILDROOT_SHA256 ?= f1c8157570bdc452163db2ff109978246cd25b4e93d3c4c9fefaeee3c4d6982d
BUILDROOT_URL = https://buildroot.org/downloads/buildroot-$(BUILDROOT_VERSION).tar.gz
BUILDROOT_SIG_URL = $(BUILDROOT_URL).sign
ROOT_DIR := $(CURDIR)
WORK_DIR := $(ROOT_DIR)/work
DL_DIR := $(ROOT_DIR)/dl
OUTPUT_DIR := $(ROOT_DIR)/output
# --- Stage-1 initramfs output (P1.10 / A1) ------------------------------------
# A SECOND, SEPARATE Buildroot output directory. This is the whole trick of the
# two-stage build (PLAN.md §5, docs/decisions/0002-initramfs.md): stage 1 is a
# different Buildroot *configuration* (static musl BusyBox, cpio) and therefore
# needs a different O=. It must never share $(OUTPUT_DIR) — that would clobber the
# main build's toolchain and target/ with the initramfs's.
#
# It is emphatically NOT BR2_TARGET_ROOTFS_INITRAMFS on the main config: that
# option embeds the whole ~300 MB target rootfs into the kernel (A1).
INITRAMFS_OUTPUT_DIR := $(ROOT_DIR)/output-initramfs
INITRAMFS_CPIO := $(INITRAMFS_OUTPUT_DIR)/images/rootfs.cpio
INITRAMFS_DEFCONFIG := $(ROOT_DIR)/configs/mister_initramfs_defconfig
INITRAMFS_INIT := $(ROOT_DIR)/board/mister/de10nano/initramfs-overlay/init
# --- RT / Linux-7.2 "beta" kernel variant (docs/rt-beta-kernel.md) ------------
# A THIRD Buildroot output dir, same trick as the initramfs stage above: a
# different kernel *configuration* (the mainline 7.2 line + PREEMPT_RT) needs its
# own O=. Deliberately not naming the -rc here: it is only a label at this point
# and configs/mister_rt.fragment is the single place that pins it.
# Since ADR 0021's 2026-07-18 amendment this is a KERNEL-ONLY build, not a
# second full image: configs/mister_kernel_defconfig (the main defconfig's
# toolchain + kernel stanzas, rootfs-tar only, no packages) with
# configs/mister_rt.fragment layered on at `make rt` time via Buildroot's own
# merge_config.sh. It produces zImage_dtb (ships as zImage_dtb-rt) plus a
# depmod'd module tree, which `rt` copies into $(EXTRA_MODULES_OVERLAY) below
# so the ONE shipped linux.img carries both kernels' modules.
# A future kernel variant `foo` needs only configs/mister_foo.fragment, its own
# foo/foo-clean/... targets mirroring the rt ones (each derives everything from
# the name), and a matrix entry in the CI workflows.
# ⚠ Never yet booted on hardware; expect to iterate. See the doc.
RT_OUTPUT_DIR := $(ROOT_DIR)/output-rt
RT_FRAGMENT := $(ROOT_DIR)/configs/mister_rt.fragment
# --- Extra-modules overlay: how variant module trees reach linux.img ----------
# The main defconfig's BR2_ROOTFS_OVERLAY lists this (gitignored, under work/)
# directory SECOND, after the tracked rootfs-overlay. Buildroot rsyncs overlays
# into TARGET_DIR at target-finalize, so whatever module trees are staged here
# (by `make rt` locally, or by CI's build-kernel job artifacts) land in the one
# shipped linux.img next to the main kernel's own tree. Empty directory ->
# byte-identical main image; `all` below guarantees it at least exists, because
# Buildroot fails on a missing overlay path.
#
# That rsync only ever ADDS or overwrites: SYSTEM_RSYNC (work/buildroot/system/
# system.mk) is `rsync -a --ignore-times` with NO --delete, and output/target/
# persists across make invocations. So REMOVING a tree from the image takes TWO
# removals -- from this overlay AND from output/target/ -- or the next
# incremental `make all` silently re-ships the copy an earlier run already
# rsynced in. The rt recipe's stale-kver branch and rt-clean both do exactly
# that pair. (CI never hits this: its output/target/ is built from scratch
# every run -- only host caches are restored.)
#
# Each variant records the kver it staged in its own stamp file (a work/
# SIBLING of the overlay, never inside it — everything inside the overlay ships
# in the image). The stamp is what lets a kernel-version bump remove its OWN
# stale tree without touching a future sibling variant's, and what rt-clean
# uses to remove exactly rt's contribution.
EXTRA_MODULES_OVERLAY := $(WORK_DIR)/extra-modules-overlay
RT_OVERLAY_STAMP := $(WORK_DIR)/extra-modules-overlay.rt-kver
# --- SD-card installer (P5.3, docs/decisions/0020-sdcard-exfat-reformat-installer.md) ---
# A FOURTH Buildroot output dir, same trick as initramfs/RT above: the installer
# that ships on sdcard.img's FAT32 partition is yet another Buildroot
# *configuration* (static-musl BusyBox cpio, plus target packages — exfatprogs,
# util-linux sfdisk — the initramfs stage doesn't need) and so needs its own O=.
#
# scripts/mk-sdcard.sh builds THIS SAME output dir itself, driving Buildroot
# directly (its own br_make helper, shaped exactly like BR_MAKE_INSTALLER
# below) as step 1/7 of assembling sdcard.img — it does not invoke the
# `installer` target. These paths are defined here anyway so that target (a
# standalone escape hatch for iterating on the installer config/overlay,
# mirroring `initramfs`/`rt`) and `clean`/`distclean` agree with that script on
# where things live. Keep in sync with scripts/mk-sdcard.sh's own
# REPO_ROOT-relative constants if either changes.
INSTALLER_OUTPUT_DIR := $(ROOT_DIR)/output-installer
INSTALLER_CPIO := $(INSTALLER_OUTPUT_DIR)/images/rootfs.cpio
INSTALLER_DEFCONFIG := $(ROOT_DIR)/configs/mister_installer_defconfig
# scripts/mk-sdcard.sh's OWN scratch dirs — mk-sdcard.sh drives its Buildroot
# invocations itself (its own br_make helper), not any target below — but these are
# still ours to clean up on clean/distclean. NONE of them is a Buildroot O= dir, so
# all three are plain rm -rf'd (never Buildroot-cleaned):
# output-installer-kernel/ - scratch HOLDING dir for mk-sdcard.sh step 2: the
# captured installer zImage_dtb plus the pre-relink
# snapshots of our real zImage_dtb / gzipped linux.img.
# Step 2 now relinks the kernel IN output/ (reusing the
# completed main build, then restoring it) rather than
# building a fourth full Buildroot tree here — a fresh
# from-scratch O= would rebuild the whole toolchain and
# blow the CI job's wall-clock cap. See mk-sdcard.sh's
# build_installer_kernel.
# output-sdcard-stage/ - fetched/staged payload (mk-sdcard.sh's STAGE_DIR).
# output-sdcard-build/ - genimage's inputs/tmp/out working dirs.
INSTALLER_KERNEL_OUTPUT_DIR := $(ROOT_DIR)/output-installer-kernel
SDCARD_STAGE_DIR := $(ROOT_DIR)/output-sdcard-stage
SDCARD_BUILD_DIR := $(ROOT_DIR)/output-sdcard-build
# --- DE25-Nano developer OS (D2.1, docs/de25-nano-tasks.md) -------------------
# A FIFTH Buildroot output dir, and by far the biggest departure of the five:
# every directory above builds for the DE10-Nano's armv7 Cyclone V. This one
# builds for a DIFFERENT BOARD — the Terasic DE25-Nano, an Intel/Altera
# Agilex 5 whose HPS is aarch64 (2x Cortex-A76 + 2x Cortex-A55). Different
# architecture, different toolchain, different kernel line (mainline 7.2.2),
# different rootfs. It shares with the main build exactly two things: the
# pinned Buildroot tree and the dl/ download cache.
#
# It follows the same trick as initramfs/rt/installer for the same reason: a
# different Buildroot *configuration* needs a different O=. Sharing
# $(OUTPUT_DIR) would clobber the DE10's armv7 toolchain with an aarch64 one —
# and Buildroot cross-toolchains bake their absolute O= path in, so the two can
# never share a host tree even if you wanted them to (ADR 0021 §3 makes the
# same point about output-rt/).
#
# UNLIKE `rt`, there is NO fragment to merge: configs/mister_de25nano_defconfig
# is a standalone, self-contained defconfig, so $(DE25_OUTPUT_DIR)/.config is a
# plain one-line `$(BR_MAKE_DE25) mister_de25nano_defconfig` rather than
# defconfig + merge_config.sh + olddefconfig. The rt fragment exists because
# that variant is a *delta* on the DE10's own kernel stanza; the DE25 shares no
# stanza with anything.
#
# ALSO UNLIKE `rt` and `all`: `de25` does NOT depend on `initramfs`. That cpio
# is an armv7 BusyBox built by configs/mister_initramfs_defconfig, and it exists
# because the DE10's real root is a loop-mounted ext4 image on a FAT partition
# that U-Boot will not load an initrd for (A3, docs/boot-chain.md). The DE25
# boots a plain ext4 root partition, so there is nothing for a stage 1 to do —
# and embedding armv7 userspace in an aarch64 kernel would produce a kernel that
# panics in a novel and confusing way. external.mk's LINUX_KCONFIG_FIXUP_CMDS
# hook is guarded off for this build; see the guard's comment there.
#
# Scope reminder, because the target name invites the wrong assumption: this is
# a BARE DEVELOPER OS. No MiSTer binaries, no DE10 packages. See ADR 0027
# Decision 6 and the defconfig's header.
DE25_OUTPUT_DIR := $(ROOT_DIR)/output-de25
BR_TARBALL := $(DL_DIR)/buildroot-$(BUILDROOT_VERSION).tar.gz
BR_DIR := $(WORK_DIR)/buildroot
# Two properties this path must have, both learned the hard way:
#
# 1. INSIDE $(BR_DIR), not a $(WORK_DIR) sibling. Make only reruns a file
# target's recipe when the target is missing or stale, so a stamp that
# outlives $(BR_DIR) (someone rm -rf's or mv's the tree away) would keep
# asserting "already present" forever. Living inside ties the stamp's
# lifetime to the tree it attests to.
#
# 2. VERSION-QUALIFIED. The $(BR_STAMP) rule has no prerequisites, so its
# recipe runs exactly once per distinct stamp filename, ever. With a
# constant name, bumping BUILDROOT_VERSION left the old stamp in place and
# Make said "Nothing to be done" — silently building against the OLD
# Buildroot tree and never even checking the new hash. That is precisely
# what P4.6's Renovate bump does, so it would have shipped broken.
# Putting the version in the filename makes a bump a different target,
# which is missing, which forces the re-fetch.
BR_STAMP := $(BR_DIR)/.mister-br2-stamp-$(BUILDROOT_VERSION)
# --- Host `install` must be GNU install ---------------------------------------
# Buildroot REFUSES to build if /usr/bin/install is uutils coreutils 0.8.0 —
# see work/buildroot/support/dependencies/dependencies.sh:193-200, which pins
# that exact version and links the upstream bug:
# https://github.com/uutils/coreutils/issues/12166
# Debian/Ubuntu's `coreutils-from-uutils` package installs exactly that as the
# default `install`, and ships GNU's as `gnuinstall`.
#
# Buildroot's own advice is `update-alternatives --install ... gnuinstall 100`,
# which needs root and mutates the developer's system. We do NOT do that. We
# instead build a tiny shim directory containing a single `install` symlink to
# whatever GNU install we can find, and prepend it to PATH for Buildroot only.
# That is self-contained, needs no root, is identical for every developer and
# for CI, and touches nothing outside this repo. It is a no-op on a host whose
# `install` is already GNU.
#
# [P1.10] The shim lives under work/, NOT under $(OUTPUT_DIR). It is a property of
# the *host*, not of any one Buildroot output, and since P1.10 there are two output
# directories that both need it. Keeping it in output/ would mean rebuilding it per
# output dir and losing it to a `make clean` of the main build.
HOSTSHIM_DIR := $(WORK_DIR)/.hostshim
GNU_INSTALL := $(shell if install --version 2>/dev/null | grep -q 'GNU coreutils'; then \
command -v install; \
else \
command -v gnuinstall 2>/dev/null; \
fi)
.PHONY: hostshim
hostshim:
@if install --version 2>/dev/null | grep -q 'GNU coreutils'; then \
exit 0; \
fi; \
if [ -z "$(GNU_INSTALL)" ]; then \
echo "FATAL: your 'install' is not GNU coreutils:" >&2; \
install --version 2>&1 | head -1 | sed 's/^/ /' >&2; \
echo "" >&2; \
echo "Buildroot refuses to build with it (dependencies.sh:193; upstream bug" >&2; \
echo "https://github.com/uutils/coreutils/issues/12166), and no GNU 'install'" >&2; \
echo "was found to substitute. Install GNU coreutils, e.g.:" >&2; \
echo " sudo apt-get install gnu-coreutils # provides /usr/bin/gnuinstall" >&2; \
exit 1; \
fi; \
mkdir -p $(HOSTSHIM_DIR); \
ln -sf $(GNU_INSTALL) $(HOSTSHIM_DIR)/install; \
echo "==> host 'install' is not GNU; shimming $(GNU_INSTALL) into PATH for Buildroot"
# Buildroot invocation shared by every forwarded target. BR2_EXTERNAL is passed
# explicitly here rather than exported: a command-line assignment overrides the
# environment anyway, so an export would just be a redundant second source of
# truth that can drift out of sync with this one.
#
# $(HOSTSHIM_DIR) goes FIRST in PATH so the GNU `install` shim (see above) wins
# over a uutils one. On a host with GNU install the directory is never created
# and this prefix is inert.
BR_MAKE = PATH="$(HOSTSHIM_DIR):$$PATH" \
$(MAKE) -C $(BR_DIR) O=$(OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR)
# The same, aimed at the stage-1 output directory. Same Buildroot tree, same
# BR2_EXTERNAL, same download cache — only O= and the defconfig differ.
BR_MAKE_INITRAMFS = PATH="$(HOSTSHIM_DIR):$$PATH" \
$(MAKE) -C $(BR_DIR) O=$(INITRAMFS_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR)
# The same, aimed at the RT/beta output directory (docs/rt-beta-kernel.md).
BR_MAKE_RT = PATH="$(HOSTSHIM_DIR):$$PATH" \
$(MAKE) -C $(BR_DIR) O=$(RT_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR)
# The same, aimed at the SD-card installer output directory. Only used by the
# standalone `installer` target below — scripts/mk-sdcard.sh builds
# $(INSTALLER_OUTPUT_DIR) itself with its own equivalent invocation.
BR_MAKE_INSTALLER = PATH="$(HOSTSHIM_DIR):$$PATH" \
$(MAKE) -C $(BR_DIR) O=$(INSTALLER_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR)
# The same, aimed at the DE25-Nano output directory (docs/de25-nano-tasks.md
# D2.1). Byte-for-byte the same shape as the four above — same Buildroot tree,
# same BR2_EXTERNAL, same dl/ cache; only O= and the defconfig differ. The
# aarch64-ness lives entirely in configs/mister_de25nano_defconfig, not here.
BR_MAKE_DE25 = PATH="$(HOSTSHIM_DIR):$$PATH" \
$(MAKE) -C $(BR_DIR) O=$(DE25_OUTPUT_DIR) BR2_EXTERNAL=$(ROOT_DIR) BR2_DL_DIR=$(DL_DIR)
# NOTE: there is no BR_MAKE_INSTALLER_KERNEL. mk-sdcard.sh's step 2 relink no longer
# builds a fourth Buildroot tree in output-installer-kernel/ — it relinks the kernel
# IN output/ (reusing the completed main build) and restores it. output-installer-
# kernel/ is now just a scratch holding dir, plain rm -rf'd by `clean`/`distclean`.
# Bare `make` must NOT be `all`. The P1.1 defconfig deliberately sets no arch or
# toolchain, so Buildroot would fall back to its own defaults (BR2_i386 +
# internal toolchain) and a reflexive `make` would spend an hour compiling an
# x86 toolchain and rootfs that nothing in this project wants. P1.2 gives the
# defconfig real content; until then, and arguably after, `help` is the right
# thing to get for free.
.DEFAULT_GOAL := help
.PHONY: all help buildroot-fetch buildroot-verify buildroot-unpack buildroot-showsig require-tools
.PHONY: clean distclean
.PHONY: initramfs initramfs-clean initramfs-menuconfig initramfs-busybox-menuconfig check-initramfs
.PHONY: rt rt-clean rt-menuconfig rt-external-deps rt-legal-info
.PHONY: de25 de25-clean de25-menuconfig de25-linux-menuconfig
.PHONY: installer installer-clean installer-menuconfig installer-busybox-menuconfig
.PHONY: sdcard
.PHONY: zimage-dtb
# GNU Make always checks whether its own makefiles need remaking, using
# whatever rule matches their name -- including the catch-all `%:` pattern
# rule below. Without this explicit no-op rule, EVERY invocation (and, worse,
# every recursive $(MAKE) call inside the $(BR_STAMP) recipe below) would
# match "Makefile" against `%: $(BR_STAMP)` and try to rebuild $(BR_STAMP)
# again before doing anything else -- which recurses without end the moment
# $(BR_STAMP)'s own recipe invokes $(MAKE) (it does, for buildroot-verify).
# An explicit rule always wins over a pattern rule for the same target name,
# so this simple line is what breaks that cycle.
Makefile: ;
# [P1.10] Exactly the same landmine, one step further out. $(INITRAMFS_DEFCONFIG) is
# a prerequisite of $(INITRAMFS_OUTPUT_DIR)/.config below. It is an existing file with
# no rule of its own, so the catch-all `%: $(BR_STAMP) hostshim` pattern rule matched
# it and make dutifully "remade" it — by forwarding a target literally named
# `/…/configs/mister_initramfs_defconfig` into Buildroot **with O=$(OUTPUT_DIR)**,
# i.e. loading the stage-1 config into the MAIN build's output directory. Caught with
# `make -n initramfs`. An explicit empty rule beats a pattern rule.
$(INITRAMFS_DEFCONFIG): ;
# Exactly the same landmine, for the SD-card installer defconfig (see the
# comment above it, and INSTALLER_OUTPUT_DIR's header comment).
$(INSTALLER_DEFCONFIG): ;
# `make` with no target builds the full image, same as bare Buildroot.
#
# TWO-STAGE (P1.10 / A1). `initramfs` is a hard prerequisite, not a convenience:
# U-Boot passes `-` for the initrd argument of `bootz` and never loads one (A3), so
# the cpio has to be INSIDE the zImage. external.mk points the kernel's
# CONFIG_INITRAMFS_SOURCE at $(INITRAMFS_CPIO) and refuses to configure the kernel
# if that file is not there — so stage 1 must have run first. Ordering it here is
# what makes a bare `make all` do the right thing.
#
# The mkdir is not decoration: the main defconfig's BR2_ROOTFS_OVERLAY names
# $(EXTRA_MODULES_OVERLAY), and Buildroot HARD-FAILS on a missing overlay path
# at target-finalize — hours into a cold build. An empty dir contributes
# nothing (byte-identical image), so creating it unconditionally is the safe
# default; `make rt` (or CI's kernel-artifact download) is what populates it.
all: initramfs $(BR_STAMP) hostshim | $(OUTPUT_DIR)/.config
@mkdir -p $(EXTRA_MODULES_OVERLAY)
$(BR_MAKE) all
@$(MAKE) --no-print-directory check-initramfs
# Self-heal a wiped output/ — and NOTHING else.
#
# Buildroot treats .config as build *input*, not output: its `all` refuses to run
# without one (work/buildroot/Makefile:981) and its `clean` deliberately keeps
# it. So `make clean && make all` was always fine, while `rm -rf output && make
# all` died with "Please configure Buildroot first". This rule closes that hole.
#
# The empty prerequisite list is the load-bearing part: with no prerequisites, an
# existing .config is always up to date and this recipe never fires again. Giving
# it the shape stage 1 uses at :253 ($(OUTPUT_DIR)/.config: <the defconfig>)
# would instead re-load the checked-in defconfig over output/.config every time
# the defconfig looked newer — silently discarding `make menuconfig` edits that
# had not been folded back with `savedefconfig`. Stage 1 can afford that; its
# config is generated, not iterated on. Stage 2's is the one people edit.
#
# $(BR_STAMP) is order-only because a parallel `make -j all` gives no ordering
# between all's own prerequisites, so this cannot rely on all's copy of it.
# The explicit rule also beats the `%:` catch-all at the bottom of this file,
# same as `Makefile: ;` and $(INITRAMFS_DEFCONFIG) above.
$(OUTPUT_DIR)/.config: | $(BR_STAMP)
@mkdir -p $(OUTPUT_DIR)
$(BR_MAKE) mister_de10nano_defconfig
# --- Cleaning -----------------------------------------------------------------
# Buildroot's vocabulary, kept on purpose: `clean` deletes what the build
# produced but KEEPS .config (work/buildroot/Makefile:1140); `distclean` also
# drops the configuration itself (:1146). The only thing widened is the scope,
# because P1.10 gave this tree two output directories and Buildroot assumes one.
#
# Without an explicit rule here, `clean` fell through the `%:` catch-all and was
# forwarded with O=$(OUTPUT_DIR) only — stage 1 was left fully built. A tree that
# is half-clean is worse than one that is not clean at all, because it looks
# fresh. The `if -d` guards keep `make clean` from being the thing that downloads
# and unpacks Buildroot just to have somewhere to run rm — which is what going
# through the catch-all ($(BR_STAMP) is one of its prerequisites) used to mean.
#
# $(BR_DIR) can vanish independently of the output dirs — it is gitignored, and
# `rm -rf work/` is how you force a re-download. Upstream cannot hit this case
# because upstream's Makefile *is* the Buildroot tree; ours is not, so the check
# is ours to make. Erroring rather than skipping is the point: Buildroot's clean
# is the only thing that knows what to delete and what to keep, so skipping it
# would report success over a still-dirty tree — this bug, again, one layer out.
clean:
@if [ ! -d $(BR_DIR) ] && { [ -d $(OUTPUT_DIR) ] || [ -d $(INITRAMFS_OUTPUT_DIR) ] || [ -d $(RT_OUTPUT_DIR) ] || [ -d $(INSTALLER_OUTPUT_DIR) ] || [ -d $(DE25_OUTPUT_DIR) ] || [ -d $(INSTALLER_KERNEL_OUTPUT_DIR) ] || [ -d $(SDCARD_STAGE_DIR) ] || [ -d $(SDCARD_BUILD_DIR) ]; }; then \
echo "FATAL: $(BR_DIR) is gone, so Buildroot's own 'clean' cannot run," >&2; \
echo " but an output directory still holds build products. Skipping" >&2; \
echo " would report success over a dirty tree." >&2; \
echo "" >&2; \
echo "Use 'make distclean' to remove all output directories outright." >&2; \
exit 1; \
fi
@if [ -d $(OUTPUT_DIR) ]; then $(BR_MAKE) clean; fi
@if [ -d $(INITRAMFS_OUTPUT_DIR) ]; then $(BR_MAKE_INITRAMFS) clean; fi
@if [ -d $(RT_OUTPUT_DIR) ]; then $(BR_MAKE_RT) clean; fi
@if [ -d $(INSTALLER_OUTPUT_DIR) ]; then $(BR_MAKE_INSTALLER) clean; fi
@if [ -d $(DE25_OUTPUT_DIR) ]; then $(BR_MAKE_DE25) clean; fi
@rm -rf $(INSTALLER_KERNEL_OUTPUT_DIR) $(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR)
@# The extra-modules overlay is a build product (staged module trees), so
@# clean takes it wholesale, stamps included — the next `make rt` restages
@# its tree, and `all` recreates the (empty) dir before Buildroot needs it.
@rm -rf $(EXTRA_MODULES_OVERLAY) $(RT_OVERLAY_STAMP)
# `rm -rf`, not a forwarded `$(BR_MAKE) distclean`, and deliberately not
# `distclean: clean` the way upstream writes it (:1146).
#
# Upstream removes $(O) itself ONLY when O is the in-tree default (:1147). Ours
# never is, so forwarding distclean would empty the directories but leave
# .config behind — landing in precisely the state that makes `make all` fail.
# Removing both output directories outright is what upstream's distclean *means*
# for an out-of-tree layout, and it makes running `clean` first dead work: there
# is nothing to preserve in a directory that is about to stop existing.
#
# dl/ deliberately survives. Upstream hardcodes `rm -rf $(TOPDIR)/dl` (:1150) and
# never $(DL_DIR) (:203) — a custom BR2_DL_DIR like ours is a shared download
# cache that distclean is not entitled to destroy. `git clean -xfd` is the real
# nothing-but-the-clone hammer; it takes work/ and dl/ with it.
distclean:
rm -rf $(OUTPUT_DIR) $(INITRAMFS_OUTPUT_DIR) $(RT_OUTPUT_DIR) \
$(INSTALLER_OUTPUT_DIR) $(DE25_OUTPUT_DIR) \
$(INSTALLER_KERNEL_OUTPUT_DIR) \
$(SDCARD_STAGE_DIR) $(SDCARD_BUILD_DIR) \
$(EXTRA_MODULES_OVERLAY) $(RT_OVERLAY_STAMP)
# --- Stage 1: the initramfs cpio ----------------------------------------------
# Phony on purpose. Buildroot is the incremental build system here; re-entering it
# is cheap when nothing changed, and it is the only thing that knows that editing
# board/mister/de10nano/initramfs-overlay/init or initramfs-busybox.config means
# the cpio must be regenerated.
initramfs: $(INITRAMFS_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INITRAMFS) all
@test -f $(INITRAMFS_CPIO) || { \
echo "FATAL: stage 1 finished but produced no $(INITRAMFS_CPIO)" >&2; exit 1; }
@$(MAKE) --no-print-directory initramfs-verify
@echo ""
@echo "==> stage-1 initramfs: $$(stat -c %s $(INITRAMFS_CPIO)) bytes ($(INITRAMFS_CPIO))"
@echo ""
# Every command /init actually invokes, asserted against the cpio we just built.
#
# This exists because of a bug that shipped silently and was only caught by booting:
# `CONFIG_ASH_BUILTIN_TEST` is the pre-1.22 spelling of BusyBox's ash `test`/`[`
# builtin (1.37 calls it `CONFIG_ASH_TEST`). kconfig does not warn about unknown
# symbols in an input .config — `olddefconfig` just DROPS them. So the config asked
# for `[`, BusyBox silently built without it, the cpio looked perfectly healthy, and
# /init died on its first `[ -n "$$root_arg" ]` with "line 98: [: not found" — while
# still printing a rescue banner that claimed the command line had no root=.
#
# A wrong applet name is a brick. Check the artifact, not the intent.
INITRAMFS_REQUIRED_APPLETS := sh mount umount losetup switch_root cttyhack setsid \
sleep mkdir cat ls dmesg tail findfs printf echo test \
rm sync
# The one non-applet binary /init runs, and the five exfatprogs ships that it must
# NOT: initramfs-post-build.sh deletes them because every byte here is a byte of
# zImage. Asserted in BOTH directions against the built cpio — a missing fsck.exfat
# makes every requested repair a silent no-op, and a resurrected mkfs.exfat puts a
# card-reformatter one typo away from the boot path. See ADR 0026.
INITRAMFS_REQUIRED_BINS := usr/sbin/fsck.exfat
INITRAMFS_FORBIDDEN_BINS := usr/sbin/dump.exfat usr/sbin/exfat2img usr/sbin/exfatlabel \
usr/sbin/mkfs.exfat usr/sbin/tune.exfat
.PHONY: initramfs-verify
initramfs-verify:
@rc=0; \
applets=$$(cpio -t --quiet < $(INITRAMFS_CPIO)); \
for a in $(INITRAMFS_REQUIRED_APPLETS); do \
echo "$$applets" | grep -qE "^(bin|sbin|usr/bin|usr/sbin)/$$a$$" || { \
echo "FATAL: /init needs '$$a' but it is not in the cpio." >&2; \
echo " Check its CONFIG_ symbol really exists in this BusyBox version —" >&2; \
echo " kconfig silently discards unknown symbols. See the header of" >&2; \
echo " board/mister/de10nano/initramfs-busybox.config." >&2; \
rc=1; }; \
done; \
for b in $(INITRAMFS_REQUIRED_BINS); do \
echo "$$applets" | grep -qx "$$b" || { \
echo "FATAL: /init needs '$$b' but it is not in the cpio." >&2; \
echo " Is BR2_PACKAGE_EXFATPROGS still set in" >&2; \
echo " configs/mister_initramfs_defconfig, and did the package move its" >&2; \
echo " install path? See ADR 0026." >&2; \
rc=1; }; \
done; \
for b in $(INITRAMFS_FORBIDDEN_BINS); do \
echo "$$applets" | grep -qx "$$b" && { \
echo "FATAL: '$$b' is in the cpio and must not be." >&2; \
echo " board/mister/de10nano/initramfs-post-build.sh is meant to delete it" >&2; \
echo " (476 KB of zImage for tools stage 1 cannot invoke). Did the" >&2; \
echo " post-build hook run? See ADR 0026." >&2; \
rc=1; }; \
done; \
echo "$$applets" | grep -qx 'init' || { \
echo "FATAL: /init is not in the cpio (the overlay did not apply)." >&2; rc=1; }; \
echo "$$applets" | grep -qx 'dev/console' || { \
echo "FATAL: /dev/console is not in the cpio — /init would have no stdio and the" >&2; \
echo " rescue shell would be unreachable. Is device creation set to STATIC?" >&2; rc=1; }; \
if command -v qemu-arm >/dev/null 2>&1; then \
qemu-arm $(INITRAMFS_OUTPUT_DIR)/target/bin/busybox ash -n $(INITRAMFS_INIT) || { \
echo "FATAL: the BusyBox ash we just built cannot even PARSE /init." >&2; \
echo " Usually a shell FEATURE that allnoconfig left off (e.g." >&2; \
echo " CONFIG_FEATURE_SH_MATH for \$$((arith))). shellcheck cannot see this:" >&2; \
echo " it checks the language, this checks the interpreter we ship." >&2; rc=1; }; \
else \
echo "WARN: qemu-arm not installed; skipping the ash -n parse check of /init." >&2; \
fi; \
[ $$rc -eq 0 ] && echo "==> initramfs-verify OK: $(words $(INITRAMFS_REQUIRED_APPLETS)) applets + $(words $(INITRAMFS_REQUIRED_BINS)) binary + $(words $(INITRAMFS_FORBIDDEN_BINS)) trimmed + /init + /dev/console + ash parses /init"; \
exit $$rc
$(INITRAMFS_OUTPUT_DIR)/.config: $(INITRAMFS_DEFCONFIG) | $(BR_STAMP)
@mkdir -p $(INITRAMFS_OUTPUT_DIR)
$(BR_MAKE_INITRAMFS) mister_initramfs_defconfig
initramfs-clean:
rm -rf $(INITRAMFS_OUTPUT_DIR)
# --- RT / Linux-7.2 beta kernel (docs/rt-beta-kernel.md) ----------------------
# Generates the variant .config by layering configs/mister_rt.fragment on the
# KERNEL-ONLY base configs/mister_kernel_defconfig with Buildroot's own
# merge_config.sh, then builds it into its own output-rt/ (shared
# toolchain sources/dl/ccache; the main output/ is untouched). Produces
# output-rt/images/zImage_dtb — the RT kernel, shipped as zImage_dtb-rt and
# selected on-device by a one-line u-boot.txt edit — plus a depmod'd module
# tree that the `rt` recipe stages into $(EXTRA_MODULES_OVERLAY) so the one
# shipped linux.img carries it.
# Order-only $(BR_STAMP), no file prerequisites — mirrors $(OUTPUT_DIR)/.config
# above (and for the same reason: a defconfig/fragment listed as a normal
# prerequisite is caught by the catch-all target-forwarding rule and would
# re-run against O=$(OUTPUT_DIR)). Re-generate after editing the fragment with
# `make rt-clean && make rt` (same manual step the main config's design implies).
$(RT_OUTPUT_DIR)/.config: | $(BR_STAMP)
@mkdir -p $(RT_OUTPUT_DIR)
$(BR_MAKE_RT) mister_kernel_defconfig
cd $(BR_DIR) && KCONFIG_CONFIG=$(RT_OUTPUT_DIR)/.config \
./support/kconfig/merge_config.sh -m -O $(RT_OUTPUT_DIR) $(RT_OUTPUT_DIR)/.config $(RT_FRAGMENT)
$(BR_MAKE_RT) olddefconfig
# `initramfs` is a hard prerequisite for the same reason it is on `all`:
# external.mk's LINUX_KCONFIG_FIXUP_CMDS hook keys on BR2_LINUX_KERNEL=y (any
# O=, this one included) and embeds the stage-1 cpio into the kernel — U-Boot
# never loads an initrd (A3), so a variant zImage without it would panic on
# the FAT root at boot, and the fixup itself hard-fails if the cpio is absent.
#
# The PREEMPT_RT assert below binds to THE kernel tree, never "the first glob
# match": `linux-[0-9]*` (not `linux-*`) so linux-firmware-*/linux-headers-*/
# linux-pam-* siblings can't match, and MORE than one kernel tree is FATAL — a
# stale sibling (kernel version bumped in the fragment without `make rt-clean`)
# sorts first often enough that picking one blindly would validate the OLD
# kernel's .config and false-pass the one guard proving the RT kernel is RT.
# The module-tree glob below is uniqueness-guarded for the same reason.
rt: initramfs $(RT_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_RT) all
@test -f $(RT_OUTPUT_DIR)/images/zImage_dtb || { \
echo "FATAL: rt build finished but produced no $(RT_OUTPUT_DIR)/images/zImage_dtb" >&2; exit 1; }
@set -- $$(ls -d $(RT_OUTPUT_DIR)/build/linux-[0-9]*/ 2>/dev/null); \
if [ $$# -eq 0 ]; then \
echo "FATAL: rt build finished but no kernel tree exists under" >&2; \
echo " $(RT_OUTPUT_DIR)/build/linux-[0-9]*/ -- cannot prove the kernel is RT." >&2; exit 1; \
elif [ $$# -gt 1 ]; then \
echo "FATAL: $$# kernel trees under $(RT_OUTPUT_DIR)/build/ -- cannot tell which one" >&2; \
echo " this build produced:" >&2; \
printf ' %s\n' "$$@" >&2; \
echo " A stale sibling appears when the fragment's kernel version is bumped" >&2; \
echo " without discarding the old tree; validating its .config could label a" >&2; \
echo " non-RT kernel zImage_dtb-rt. Run 'make rt-clean && make rt'." >&2; exit 1; \
fi; \
cfg="$$1.config"; \
if [ ! -f "$$cfg" ]; then \
echo "FATAL: rt build finished but $$cfg" >&2; \
echo " does not exist -- cannot prove the kernel is RT." >&2; exit 1; \
fi; \
grep -qx 'CONFIG_PREEMPT_RT=y' "$$cfg" || { \
echo "FATAL: the built RT kernel is NOT RT: CONFIG_PREEMPT_RT=y is absent from" >&2; \
echo " $$cfg" >&2; \
echo " merge_config.sh only WARNS when a fragment symbol is dropped, and" >&2; \
echo " olddefconfig silently discards symbols whose dependencies fail -- so" >&2; \
echo " without this check a plain 7.2 kernel would ship labeled zImage_dtb-rt." >&2; \
echo " Two fragment layers are in play and a reviewer has already confused" >&2; \
echo " them: board/mister/de10nano/linux-rt.fragment is the KERNEL-config" >&2; \
echo " layer where CONFIG_PREEMPT_RT lives, wired in via the BUILDROOT-config" >&2; \
echo " layer configs/mister_rt.fragment. Check the kernel-config layer against" >&2; \
echo " this kernel version's Kconfig (docs/rt-beta-kernel.md §1)." >&2; exit 1; }
@# Stage the depmod'd module tree into the extra-modules overlay so the next
@# `make all` folds it into linux.img. The stamp records which kver rt owns
@# in the overlay: on a kernel-version bump the OLD tree is removed by name,
@# from the overlay AND from output/target/ (the overlay rsync never deletes
@# -- see the EXTRA_MODULES_OVERLAY header -- so an overlay-only removal
@# would leave an incremental `make all` shipping THREE module trees). A
@# future sibling variant's tree, under its own stamp, is never touched.
@set -e; \
set -- $$(ls -d $(RT_OUTPUT_DIR)/target/usr/lib/modules/*/ 2>/dev/null); \
if [ $$# -ne 1 ]; then \
echo "FATAL: expected exactly one module tree under" >&2; \
echo " $(RT_OUTPUT_DIR)/target/usr/lib/modules/ but found $$#." >&2; \
echo " Zero means depmod/target-finalize never ran (is BR2_TARGET_ROOTFS_TAR" >&2; \
echo " still set in configs/mister_kernel_defconfig?); more than one is the" >&2; \
echo " stale-sibling hazard described above. Run 'make rt-clean && make rt'." >&2; exit 1; \
fi; \
kver=$$(basename "$$1"); \
test -s "$$1/modules.alias" || { \
echo "FATAL: $$1/modules.alias is missing or empty -- depmod did not run at" >&2; \
echo " target-finalize, so this tree cannot autoload modules on device." >&2; exit 1; }; \
if [ -f $(RT_OVERLAY_STAMP) ]; then \
old=$$(cat $(RT_OVERLAY_STAMP)); \
if [ -n "$$old" ] && [ "$$old" != "$$kver" ]; then \
rm -rf "$(EXTRA_MODULES_OVERLAY)/usr/lib/modules/$$old" \
"$(OUTPUT_DIR)/target/usr/lib/modules/$$old"; \
echo "==> removed rt's stale $$old module tree (overlay + output/target)"; \
fi; \
fi; \
mkdir -p $(EXTRA_MODULES_OVERLAY)/usr/lib/modules; \
rm -rf "$(EXTRA_MODULES_OVERLAY)/usr/lib/modules/$$kver"; \
cp -a "$$1" "$(EXTRA_MODULES_OVERLAY)/usr/lib/modules/$$kver"; \
echo "$$kver" > $(RT_OVERLAY_STAMP); \
echo ""; \
echo "==> RT kernel: $(RT_OUTPUT_DIR)/images/zImage_dtb (ship as zImage_dtb-rt — docs/rt-beta-kernel.md)"; \
echo "==> RT modules: $$kver staged into $(EXTRA_MODULES_OVERLAY)/usr/lib/modules/"; \
echo " (folded into linux.img by the next 'make all'; 'make rt-clean' removes them)"; \
echo ""
# Edit the RT kernel .config interactively (writes back to output-rt/.config).
rt-menuconfig: $(RT_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_RT) linux-menuconfig
# Buildroot's own `external-deps` / `legal-info`, run against the RT config
# (now the kernel-only one — same target names, much smaller package graph).
# Explicit rules rather than the `%:` catch-all at the bottom, because the
# catch-all forwards with O=$(OUTPUT_DIR) — i.e. against the MAIN build. Same
# reason `rt`/`rt-menuconfig` exist; same rt-* naming precedent. CI leans on
# both: rt-external-deps is the dl/-completeness oracle for the RT variant's
# download cache (it runs with -B upstream, so it ignores stamps), and
# rt-legal-info produces the kernel-source SBOM/GPL bundle for release assets.
rt-external-deps: $(RT_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_RT) external-deps
rt-legal-info: $(RT_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_RT) legal-info
# Removes rt's overlay contribution too (by the kver its stamp recorded), AND
# the same tree from output/target/: Buildroot's overlay rsync never deletes
# (see the EXTRA_MODULES_OVERLAY header), so once a `make all` has rsynced the
# tree into output/target/ an overlay-only removal cannot un-ship it -- every
# later incremental `make all` would quietly carry it forever. With both
# removals, "rt-clean then make all" really yields a main-only image. Both rms
# are harmless when their path is absent (fresh checkout, no output/ yet).
# The [ -n "$$kver" ] guard matters doubly now: an empty stamp would otherwise
# turn the rm into `rm -rf .../usr/lib/modules/` -- every variant's tree in
# the overlay, and the MAIN kernel's tree in output/target/.
rt-clean:
@if [ -f $(RT_OVERLAY_STAMP) ]; then \
kver=$$(cat $(RT_OVERLAY_STAMP)); \
if [ -n "$$kver" ]; then \
rm -rf "$(EXTRA_MODULES_OVERLAY)/usr/lib/modules/$$kver" \
"$(OUTPUT_DIR)/target/usr/lib/modules/$$kver"; \
echo "==> removed rt's $$kver module tree (overlay + output/target)"; \
fi; \
rm -f $(RT_OVERLAY_STAMP); \
fi
rm -rf $(RT_OUTPUT_DIR)
# --- DE25-Nano developer OS (D2.1, docs/de25-nano-tasks.md) -------------------
# Loads configs/mister_de25nano_defconfig into output-de25/. Order-only
# $(BR_STAMP) and NO file prerequisite on the defconfig — same shape, same two
# reasons, as $(OUTPUT_DIR)/.config and $(RT_OUTPUT_DIR)/.config above: a
# defconfig listed as a normal prerequisite gets caught by the `%:` catch-all
# target-forwarding rule at the bottom of this file and would be "remade" with
# O=$(OUTPUT_DIR) (i.e. loaded into the DE10's output dir — here that would
# mean loading an AARCH64 config over the armv7 build, which is about as bad as
# this class of bug gets), and with no prerequisites an existing .config is
# always up to date, so `make de25-menuconfig` edits are not silently
# discarded by the next `make de25`.
#
# Re-generate after editing the defconfig with `make de25-clean && make de25`,
# the same manual step the main and rt configs imply.
#
# No merge_config.sh step: unlike `rt`, this defconfig is standalone.
# `hostshim` is an order-only prerequisite HERE, not only on `de25`: under
# `make -j de25` the sibling prerequisites of `de25` run concurrently, so the
# config recipe (which invokes Buildroot, whose dependency check needs the
# shim's `install` on PATH) could otherwise start before the shim exists.
$(DE25_OUTPUT_DIR)/.config: | $(BR_STAMP) hostshim
@mkdir -p $(DE25_OUTPUT_DIR)
$(BR_MAKE_DE25) mister_de25nano_defconfig
# Deliberately NOT `de25: initramfs ...` — see DE25_OUTPUT_DIR's header for why
# the stage-1 cpio has no business in an aarch64 kernel.
#
# The post-build assertions are the DE25's equivalent of `rt`'s
# CONFIG_PREEMPT_RT check and `initramfs`'s cpio check: Buildroot exits 0 on
# plenty of configurations that produce no bootable artifact, and on a board
# with no hardware validation yet the build must say so at the end rather than
# leave someone to discover it at a dead serial console.
#
# The .dtb is asserted by GLOB rather than by name on purpose. The name is
# owned by the defconfig (BR2_LINUX_KERNEL_CUSTOM_DTS_PATH names the board
# file, board/mister/de25nano/socfpga_agilex5_de25nano.dts; it was mainline's
# socdk placeholder until D2.3 landed). A hardcoded filename here would fail
# the build on the day the DTS is renamed or a second variant is added, for a
# reason that has nothing to do with what went wrong. Buildroot
# installs the dtb into images/ under its BASENAME (linux/linux.mk:491-497,
# `notdir` unless BR2_LINUX_KERNEL_DTB_KEEP_DIRNAME), so a flat glob sees it
# either way. Zero dtbs IS a failure: BR2_LINUX_KERNEL_DTS_SUPPORT is on, so an
# empty images/*.dtb means the DTS silently did not build.
de25: $(DE25_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_DE25) all
@test -f $(DE25_OUTPUT_DIR)/images/Image || { \
echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/Image" >&2; \
echo " (BR2_LINUX_KERNEL_IMAGE=y selects the uncompressed aarch64 'Image'" >&2; \
echo " target -- if the defconfig was changed to Image.gz, change this" >&2; \
echo " assertion in the same commit.)" >&2; exit 1; }
@set -- $$(ls $(DE25_OUTPUT_DIR)/images/*.dtb 2>/dev/null); \
if [ $$# -eq 0 ]; then \
echo "FATAL: de25 build finished but installed no device tree blob into" >&2; \
echo " $(DE25_OUTPUT_DIR)/images/ -- BR2_LINUX_KERNEL_DTS_SUPPORT is set," >&2; \
echo " so this means the DTS named by BR2_LINUX_KERNEL_INTREE_DTS_NAME /" >&2; \
echo " BR2_LINUX_KERNEL_CUSTOM_DTS_PATH did not build." >&2; exit 1; \
fi; \
echo ""; \
echo "==> DE25 kernel: $(DE25_OUTPUT_DIR)/images/Image ($$(stat -c %s $(DE25_OUTPUT_DIR)/images/Image) bytes)"; \
for d in "$$@"; do echo "==> DE25 dtb: $$d ($$(stat -c %s $$d) bytes)"; done; \
test -f $(DE25_OUTPUT_DIR)/images/rootfs.ext4 || { \
echo "FATAL: de25 build finished but produced no $(DE25_OUTPUT_DIR)/images/rootfs.ext4" >&2; \
echo " (BR2_TARGET_ROOTFS_EXT2 + _EXT2_4 select it -- a config that emits no" >&2; \
echo " rootfs is not a green build, whatever the kernel did.)" >&2; exit 1; }; \
echo "==> DE25 rootfs: $(DE25_OUTPUT_DIR)/images/rootfs.ext4 ($$(stat -c %s $(DE25_OUTPUT_DIR)/images/rootfs.ext4) bytes)"; \
echo " Bare developer OS -- no MiSTer binaries, no bootloader yet (D2.2)."; \
echo ""
# Escape hatches for iterating without hand-editing the checked-in defconfig.
# Both write to output-de25/; fold the result back into
# configs/mister_de25nano_defconfig (`savedefconfig`, then hand-restore the
# header comments -- see that file's own note) or into
# board/mister/de25nano/linux.fragment by hand.
#
# de25-linux-menuconfig exists as its own target for the same reason
# rt-menuconfig does: the `%:` catch-all would forward a bare
# `make linux-menuconfig` with O=$(OUTPUT_DIR), i.e. against the DE10.
de25-menuconfig: $(DE25_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_DE25) menuconfig
de25-linux-menuconfig: $(DE25_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_DE25) linux-menuconfig
# Plain rm -rf, like initramfs-clean/installer-clean and unlike rt-clean: this
# build stages nothing into the extra-modules overlay and contributes nothing
# to output/, so there is no second removal to pair with.
de25-clean:
rm -rf $(DE25_OUTPUT_DIR)
# --- SD-card installer (P5.3, docs/decisions/0020-sdcard-exfat-reformat-installer.md) ---
# Builds ONLY the installer initramfs cpio, standalone. scripts/mk-sdcard.sh
# builds this exact output dir itself as step 1/7 of `make sdcard` (see
# INSTALLER_OUTPUT_DIR's header comment above) — this target is the escape
# hatch for iterating on configs/mister_installer_defconfig or
# board/mister/de10nano/installer-overlay/ without running the whole sdcard
# pipeline, mirroring `initramfs` above.
$(INSTALLER_OUTPUT_DIR)/.config: $(INSTALLER_DEFCONFIG) | $(BR_STAMP)
@mkdir -p $(INSTALLER_OUTPUT_DIR)
$(BR_MAKE_INSTALLER) mister_installer_defconfig
installer: $(INSTALLER_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INSTALLER) all
@test -f $(INSTALLER_CPIO) || { \
echo "FATAL: installer build finished but produced no $(INSTALLER_CPIO)" >&2; exit 1; }
@echo ""
@echo "==> installer cpio: $$(stat -c %s $(INSTALLER_CPIO)) bytes ($(INSTALLER_CPIO))"
@echo ""
installer-menuconfig: $(INSTALLER_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INSTALLER) menuconfig
installer-busybox-menuconfig: $(INSTALLER_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INSTALLER) busybox-menuconfig
installer-clean:
rm -rf $(INSTALLER_OUTPUT_DIR)
# Escape hatches for iterating on stage 1 without hand-editing the checked-in
# configs. Both write to output-initramfs/; remember to fold the result back into
# configs/mister_initramfs_defconfig (`savedefconfig`) or into
# board/mister/de10nano/initramfs-busybox.config by hand.
initramfs-menuconfig: $(INITRAMFS_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INITRAMFS) menuconfig
initramfs-busybox-menuconfig: $(INITRAMFS_OUTPUT_DIR)/.config hostshim
$(BR_MAKE_INITRAMFS) busybox-menuconfig
# --- The assertion that stops a silent brick ----------------------------------
# docs/boot-chain.md §8, I1 and I2. The failure this guards against is not loud: a
# kernel built with CONFIG_INITRAMFS_SOURCE="" boots perfectly, runs the kernel's
# own default_cpio_list rootfs, finds no /init, calls prepare_namespace(), tries to
# mount root=/dev/mmcblk0p1 (a FAT partition) as a root filesystem, and panics —
# with a message that points at the disk, not at the build. Fail at build time
# instead. Also run standalone: `make check-initramfs`.
check-initramfs:
@cfg=$$(ls -d $(OUTPUT_DIR)/build/linux-*/ 2>/dev/null | head -1)".config"; \
if [ ! -f "$$cfg" ]; then \
echo "==> check-initramfs: no kernel build in $(OUTPUT_DIR) yet — nothing to check."; \
echo " (P1.3 owns turning BR2_LINUX_KERNEL on in the main defconfig.)"; \
exit 0; \
fi; \
rc=0; \
grep -qx 'CONFIG_BLK_DEV_INITRD=y' "$$cfg" || { \
echo "FAIL (I1): CONFIG_BLK_DEV_INITRD is not y in $$cfg" >&2; rc=1; }; \
grep -q '^CONFIG_INITRAMFS_SOURCE=".\+"' "$$cfg" || { \
echo "FAIL (I2): CONFIG_INITRAMFS_SOURCE is empty in $$cfg — the kernel has NO" >&2; \
echo " initramfs. It will panic on a FAT root at boot." >&2; rc=1; }; \
[ $$rc -eq 0 ] && echo "==> check-initramfs OK: $$(grep '^CONFIG_INITRAMFS_SOURCE=' $$cfg)"; \
exit $$rc
# --- zImage_dtb (P1.11 / A3) ----------------------------------------------------
# The REAL hook is BR2_ROOTFS_POST_IMAGE_SCRIPT in configs/mister_de10nano_defconfig
# (board/mister/de10nano/post-image.sh), which Buildroot runs automatically at the
# end of every `$(BR_MAKE) all` and which already fails the build on a contract
# violation -- so `make all` needs no extra step here, unlike check-initramfs above
# (nothing else asserts THAT one).
#
# This target exists for standalone use: iterating on post-image.sh / linux.config
# without a full `make all`, or (until P1.3's Buildroot-level kernel wiring lands)
# pointing it at any pre-built zImage+DTB pair via ZIMAGE_DTB_BINARIES_DIR=, e.g. a
# scratch dir populated from a raw kbuild tree such as work/k-final:
# make zimage-dtb ZIMAGE_DTB_BINARIES_DIR=/path/to/scratch/images
# (post-image.sh also searches BINARIES_DIR/../build/.../arch/arm/boot/ if the
# image is not directly in BINARIES_DIR -- see its own header.)
ZIMAGE_DTB_BINARIES_DIR ?= $(OUTPUT_DIR)/images
zimage-dtb:
@mkdir -p $(ZIMAGE_DTB_BINARIES_DIR)
$(ROOT_DIR)/board/mister/de10nano/post-image.sh $(ZIMAGE_DTB_BINARIES_DIR)
# --- Full SD-card image (P5.3, docs/decisions/0020-sdcard-exfat-reformat-installer.md) ---
# Runs scripts/mk-sdcard.sh, the orchestrator that builds the installer cpio +
# relinked installer kernel, fetches/stages the mr-fusion-parity payload, and
# assembles + xz's the dd/Etcher-writable output/images/sdcard.img(.xz) (or
# sdcard-full.img(.xz) when SDCARD_CORES=1). See that script's own header for
# its seven steps — it drives Buildroot directly for the installer pieces and
# does NOT go through the `installer` target above.
#
# Requires a COMPLETED `make rt` THEN `make all` first — the ORDER matters:
# mk-sdcard.sh snapshots output/images/linux.img as-built, and only a `make
# all` that runs AFTER `make rt` has folded the RT module tree (staged in the
# extra-modules overlay) into that image. Built the other way round, the card
# would stage zImage_dtb-rt next to a linux.img with no matching modules —
# docs/rt-beta-kernel.md §5's silent broken-peripherals failure. mk-sdcard.sh
# reads output/images/{linux.img,zImage_dtb} (the real, Downloader-shipped
# outputs) plus output-rt/images/zImage_dtb (shipped on the card as
# zImage_dtb-rt so one flashable card carries both kernels — override the
# source with MISTER_RT_ZIMAGE=), fails loudly, by name, if any is missing,
# and cross-checks linux.img against the overlay's staged kver(s) so an
# out-of-order build dies there instead of shipping.
# Deliberately NOT made an `all`/`rt`-dependent prerequisite here — those are
# multi-hour builds, and a script that already checks its own prerequisites
# should not have that check duplicated (and silently re-triggered) at the
# Make level. Mirrors `rt`/`initramfs` in every other respect: a phony target,
# hostshim ensured first, SDCARD_CORES passed straight through from the
# environment/command line.
SDCARD_CORES ?= 0
sdcard: hostshim
SDCARD_CORES=$(SDCARD_CORES) $(ROOT_DIR)/scripts/mk-sdcard.sh
# Deliberately does NOT depend on $(BR_STAMP): `make help` on a fresh clone (or
# with no network) must print something useful rather than trying to fetch a
# tarball first. Buildroot's own target list is behind `make br-help`, which
# does need the tree.
help:
@echo "MiSTer BR2_EXTERNAL wrapper (TASKS.md P1.1)"
@echo ""
@echo " make mister_de10nano_defconfig - load configs/mister_de10nano_defconfig"
@echo " make menuconfig - interactive Buildroot config"
@echo " make linux-menuconfig - interactive kernel config"
@echo " make savedefconfig - save current config back to a defconfig"
@echo " make olddefconfig - non-interactively resolve config to defaults"
@echo " make list-defconfigs - list built-in and external defconfigs"
@echo " make buildroot-verify - download (if needed) + SHA-256-verify the"
@echo " pinned Buildroot tarball, without unpacking"
@echo " make buildroot-showsig - print upstream's GPG-signed release manifest"
@echo " (the ONLY valid source for BUILDROOT_SHA256)"
@echo " make br-help - Buildroot's own target list"
@echo " make all - build the full image (runs 'initramfs' first)"
@echo ""
@echo "Cleaning (Buildroot's meanings, applied to ALL output dirs):"
@echo " make clean - delete everything the build produced,"
@echo " KEEPING all .config files"
@echo " make distclean - rm -rf output/, output-initramfs/, output-rt/,"
@echo " output-installer/, output-de25/, the sdcard"
@echo " staging dirs and"
@echo " the extra-modules overlay, .config included;"
@echo " dl/ is kept (it is a shared cache —"
@echo " 'git clean -xfd' takes it)"
@echo ""
@echo "Two-stage initramfs (P1.10):"
@echo " make initramfs - build ONLY the stage-1 cpio and print its size"
@echo " make initramfs-menuconfig - Buildroot menuconfig for the stage-1 config"
@echo " make initramfs-busybox-menuconfig - BusyBox menuconfig for the stage-1 BusyBox"
@echo " make initramfs-clean - rm -rf output-initramfs/"
@echo " make check-initramfs - assert the built kernel really embeds the cpio"
@echo ""
@echo "RT / Linux-7.2 beta kernel (docs/rt-beta-kernel.md):"
@echo " make rt - kernel-only build of the PREEMPT_RT variant into"
@echo " output-rt/ (asserts CONFIG_PREEMPT_RT=y), then"
@echo " stage its module tree into the extra-modules"
@echo " overlay so the next 'make all' ships it in"
@echo " linux.img"
@echo " make rt-menuconfig - kernel menuconfig for the RT variant"
@echo " make rt-external-deps - list every download the RT config needs (CI's"
@echo " dl/-completeness oracle)"
@echo " make rt-legal-info - legal-info (SBOM + kernel sources) for the RT"
@echo " variant"
@echo " make rt-clean - rm -rf output-rt/ + remove rt's module tree"
@echo " from the extra-modules overlay AND output/target"
@echo " (Buildroot's overlay rsync never deletes)"
@echo ""
@echo "DE25-Nano developer OS (aarch64 / Agilex 5 -- docs/de25-nano-tasks.md D2.1):"
@echo " make de25 - build the DE25-Nano image into output-de25/"
@echo " (aarch64 toolchain + mainline 7.2.2 kernel +"
@echo " minimal BusyBox ext4 rootfs; asserts images/Image"
@echo " and a .dtb exist). BARE DEVELOPER OS: no MiSTer"
@echo " binaries, no bootloader yet. Does NOT run"
@echo " 'initramfs' -- that cpio is armv7."
@echo " make de25-menuconfig - Buildroot menuconfig for the DE25 config"
@echo " make de25-linux-menuconfig - kernel menuconfig for the DE25 kernel"
@echo " make de25-clean - rm -rf output-de25/"
@echo ""
@echo "zImage_dtb assembly (P1.11):"
@echo " make zimage-dtb - cat zImage+DTB and run scripts/check-zimage-dtb.sh"
@echo " (runs automatically at the end of 'make all' via"
@echo " BR2_ROOTFS_POST_IMAGE_SCRIPT; override the source"
@echo " dir with ZIMAGE_DTB_BINARIES_DIR=)"
@echo ""
@echo "SD-card installer image (P5.3, ADR 0020):"
@echo " make sdcard - after 'make all' (the card ships no variant"
@echo " kernel, so 'make rt' is optional; if you do"
@echo " build rt, do it FIRST so 'all' folds its"
@echo " modules into linux.img), run"
@echo " scripts/mk-sdcard.sh to produce"
@echo " output/images/sdcard.img(.xz)"
@echo " (SDCARD_CORES=1 for sdcard-full.img(.xz))"
@echo " make installer - build ONLY the installer stage-1 cpio and print"
@echo " its size (escape hatch; mk-sdcard.sh builds this"
@echo " same output-installer/ itself)"
@echo " make installer-menuconfig - Buildroot menuconfig for the installer config"
@echo " make installer-busybox-menuconfig - BusyBox menuconfig for the installer BusyBox"
@echo " make installer-clean - rm -rf output-installer/"
@echo ""
@echo "Pinned Buildroot: $(BUILDROOT_VERSION) (BR2_EXTERNAL=$(ROOT_DIR))"
@echo "Any other target is forwarded verbatim into Buildroot's own Makefile."
br-help: $(BR_STAMP) hostshim
$(BR_MAKE) help
# Print upstream's clearsigned release manifest, which carries the authoritative
# SHA256 line. Use this — not `sha256sum` of a tarball you just downloaded —
# whenever BUILDROOT_VERSION is bumped.
buildroot-showsig:
@echo "==> $(BUILDROOT_SIG_URL)"
@curl -fsSL $(BUILDROOT_SIG_URL) || { \
echo "FATAL: could not fetch the release signature." >&2; exit 1; }
# Fail fast and by name, the way scripts/inventory/common.sh's mrl_require does,
# rather than dying deep inside a recipe with "curl: command not found".
require-tools:
@for t in curl tar sha256sum; do \
command -v $$t >/dev/null 2>&1 || { \
echo "FATAL: required tool '$$t' not found in PATH." >&2; exit 1; }; \
done
# --- Download, verify, unpack Buildroot ---------------------------------------
# Plain file-based rule: only fetches if $(BR_TARBALL) isn't already on disk.
# Download to .tmp and rename only on success, so an interrupted transfer can
# never leave a truncated tarball parked at the real path.
$(BR_TARBALL): | require-tools
@mkdir -p $(DL_DIR)
@echo "==> Downloading Buildroot $(BUILDROOT_VERSION) from $(BUILDROOT_URL)"
@curl -fSL --retry 3 -o $@.tmp $(BUILDROOT_URL)
@mv $@.tmp $@
# Downloads (if needed) and checks the tarball against the pinned hash.
# Standalone and directly invokable so the verification path can be exercised
# (and its failure mode demonstrated) without touching work/buildroot.
buildroot-fetch: $(BR_TARBALL)