Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
4043 lines (3824 loc) · 207 KB
/
Copy pathMakefile
File metadata and controls
4043 lines (3824 loc) · 207 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
.PHONY: all clean test compiler examples ci ci-sweep
# Pin the default goal to `all` regardless of rule order. Without this, the
# first FILE-target rule defined becomes the default goal — and the generated
# version-header rule ($(VERSION_HEADER)) is defined above `all:`, so a bare
# `make` would build only the header. Explicit default goal makes rule order
# irrelevant.
.DEFAULT_GOAL := all
# Detect OS and shell environment.
# WINDOWS_NATIVE is set only for pure Windows (mingw32-make + cmd.exe).
# IS_WINDOWS is set for any Windows variant (native, MSYS2, MinGW, Cygwin).
WINDOWS_NATIVE :=
IS_WINDOWS :=
# WINDOWS=1 — cross-build FOR Windows from a non-Windows host (see the
# "Windows cross-build" block below). Declared here, ahead of host
# detection, because every downstream conditional keys off IS_WINDOWS /
# EXE_EXT: the cross build wants the Windows CODE PATHS with a Linux
# host's shell utilities, so it sets those two and leaves WINDOWS_NATIVE
# (which switches to cmd.exe-isms like `if not exist`) unset.
ifeq ($(WINDOWS),1)
IS_WINDOWS := 1
EXE_EXT := .exe
DETECTED_OS := MINGW64_NT-cross
endif
ifeq ($(OS),Windows_NT)
IS_WINDOWS := 1
_UNAME_S := $(shell uname -s 2>&1)
ifneq ($(findstring MINGW,$(_UNAME_S)),)
DETECTED_OS := $(_UNAME_S)
EXE_EXT := .exe
else ifneq ($(findstring MSYS,$(_UNAME_S)),)
DETECTED_OS := $(_UNAME_S)
EXE_EXT := .exe
else ifneq ($(findstring CYGWIN,$(_UNAME_S)),)
DETECTED_OS := $(_UNAME_S)
EXE_EXT := .exe
else
DETECTED_OS := Windows
EXE_EXT := .exe
WINDOWS_NATIVE := 1
endif
else ifneq ($(WINDOWS),1)
DETECTED_OS := $(shell uname -s)
ifneq ($(findstring MINGW,$(DETECTED_OS)),)
EXE_EXT := .exe
IS_WINDOWS := 1
else ifneq ($(findstring MSYS,$(DETECTED_OS)),)
EXE_EXT := .exe
IS_WINDOWS := 1
else ifneq ($(findstring CYGWIN,$(DETECTED_OS)),)
EXE_EXT := .exe
IS_WINDOWS := 1
else
EXE_EXT :=
endif
endif
ifdef WINDOWS_NATIVE
PATH_SEP := \\
MKDIR := if not exist
RM := del /Q
RM_DIR := rd /S /Q
else
PATH_SEP := /
MKDIR := mkdir -p
RM := rm -f
RM_DIR := rm -rf
endif
# Parallel job count (override with: make test-ae NPROC=8)
ifdef WINDOWS_NATIVE
NPROC ?= $(shell echo %NUMBER_OF_PROCESSORS% 2>nul || echo 4)
else
NPROC ?= $(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)
endif
# Version: the VERSION FILE IN THIS TREE is authoritative; the highest git tag
# is only a fallback for an untagged dev checkout.
#
# This ordering used to be reversed ("prefer highest git tag"), which mislabels
# a tagged release build. `git tag -l` reports the tags VISIBLE IN THIS CLONE,
# and release.yml's build jobs check out shallow, so the visible set can be
# stale or partial — `tail -1` then lands on an OLDER tag and overrides the
# correct VERSION file. Measured: v0.516.0 shipped an `ae --version` saying
# 0.417.0, while `git show v0.516.0:VERSION` said 0.516.0 all along. The tree
# was right and the tag scan was wrong.
#
# For a tagged build the two should be identical, so preferring the tree costs
# nothing and is immune to clone depth. For a tarball there is no git at all.
# Only an untagged dev checkout — where VERSION is still the 0.0.0 placeholder
# — consults tags, so `make` on a plain main still reports a real-ish number.
#
# One arm now, not two: `cat` covers Windows too (the WINDOWS_NATIVE split
# existed because `git tag` shell-outs are awkward under cmd.exe; a file read
# is not). WINDOWS_NATIVE keeps `type` since cmd.exe has no `cat`.
ifdef WINDOWS_NATIVE
VERSION := $(shell type VERSION 2>nul || echo 0.0.0)
else
VERSION := $(shell cat VERSION 2>/dev/null | tr -d '[:space:]')
ifeq ($(VERSION),)
VERSION := 0.0.0
endif
ifeq ($(VERSION),0.0.0)
VERSION := $(shell git tag -l 'v*.*.*' 2>/dev/null | sed 's/^v//' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)
ifeq ($(VERSION),)
VERSION := 0.0.0
endif
endif
endif
# Numeric version components, split from $(VERSION) (MAJOR.MINOR.PATCH). A
# trailing pre-release suffix on PATCH (e.g. "0-dev") is stripped to digits.
# These feed the generated runtime/aether_version.h so the macro, the two
# VERSION sidecars, and `aetherc --version` all derive from one source.
VERSION_MAJOR := $(shell echo "$(VERSION)" | cut -d. -f1 | tr -dc 0-9)
VERSION_MINOR := $(shell echo "$(VERSION)" | cut -d. -f2 | tr -dc 0-9)
VERSION_PATCH := $(shell echo "$(VERSION)" | cut -d. -f3 | tr -dc 0-9)
# MAJOR*1_000_000 + MINOR*1_000 + PATCH — the monotonic form for #if gating.
VERSION_NUM := $(shell echo $$(( $(VERSION_MAJOR)*1000000 + $(VERSION_MINOR)*1000 + $(VERSION_PATCH) )))
# Generated, dependency-free version header. Baked into the exact header bytes
# consumers compile against (installed to include/aether/runtime/ by the normal
# `find runtime -name '*.h'` walk), so it cannot desync from the compiler.
# GENERATED — do not commit / edit by hand (gitignored).
#
# This is a real FILE target (depends on VERSION + Makefile), NOT a phony one:
# a phony target used as a build prerequisite makes GNU make treat the depending
# target as perpetually out-of-date in ways that can short-circuit its recipe.
# Build targets take it as an ORDER-ONLY prerequisite (`| $(VERSION_HEADER)`),
# so it's built if missing but never forces a rebuild. It's regenerated only
# when VERSION or the Makefile changes, and written atomically (tmp + mv) and
# only on content change, so the tree is never spuriously dirtied.
VERSION_HEADER := runtime/aether_version.h
$(VERSION_HEADER): VERSION Makefile
@tmp="$(VERSION_HEADER).tmp.$$$$"; \
{ \
echo "/* Aether SDK version — GENERATED by the Makefile from \$$(VERSION)."; \
echo " * Do not edit by hand. Dependency-free (#defines only) so anything,"; \
echo " * including the --emit=csrc prologue, can include it cheaply. This is"; \
echo " * the static, drift-proof complement to AetherLibMeta.aether_version"; \
echo " * (per-artifact runtime metadata). */"; \
echo "#ifndef AETHER_VERSION_H"; \
echo "#define AETHER_VERSION_H"; \
echo ""; \
echo "#define AETHER_VERSION \"$(VERSION)\""; \
echo "#define AETHER_VERSION_MAJOR $(VERSION_MAJOR)"; \
echo "#define AETHER_VERSION_MINOR $(VERSION_MINOR)"; \
echo "#define AETHER_VERSION_PATCH $(VERSION_PATCH)"; \
echo "/* MAJOR*1000000 + MINOR*1000 + PATCH — monotonic, for #if gating:"; \
echo " * #if AETHER_VERSION_NUM < 390000"; \
echo " * # error \"requires Aether >= 0.390\""; \
echo " * #endif */"; \
echo "#define AETHER_VERSION_NUM $(VERSION_NUM)"; \
echo ""; \
echo "#endif /* AETHER_VERSION_H */"; \
} > "$$tmp"; \
if [ -f "$(VERSION_HEADER)" ] && cmp -s "$$tmp" "$(VERSION_HEADER)"; then \
rm -f "$$tmp"; \
else \
mv "$$tmp" "$(VERSION_HEADER)"; \
echo "Generated $(VERSION_HEADER) (v$(VERSION))"; \
fi
# Convenience phony alias for explicit regeneration (e.g. `make gen-version-header`).
.PHONY: gen-version-header
gen-version-header: $(VERSION_HEADER)
# #1366: the bare C symbols the stdlib exposes. Codegen renames a user function
# that would collide with one, so this must track std/ automatically: a
# hand-kept list is what let string_replace_all reach a release.
# GENERATED, do not commit or edit by hand (gitignored).
STDLIB_SYMS_HEADER := compiler/codegen/aether_stdlib_symbols.h
STDLIB_MODULE_AE := $(wildcard std/*/module.ae) $(wildcard std/*/*/module.ae)
$(STDLIB_SYMS_HEADER): $(STDLIB_MODULE_AE) Makefile
@tmp="$(STDLIB_SYMS_HEADER).tmp.$$$$"; \
{ \
echo "/* GENERATED by the Makefile from the std module.ae files. Do not edit. */"; \
echo "#ifndef AETHER_STDLIB_SYMBOLS_H"; \
echo "#define AETHER_STDLIB_SYMBOLS_H"; \
echo "/* Sorted for bsearch. */"; \
echo "static const char* const AETHER_STDLIB_SYMBOLS[] = {"; \
grep -rhoE '^extern[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' $(STDLIB_MODULE_AE) 2>/dev/null \
| awk '{print $$2}' | LC_ALL=C sort -u | sed 's/.*/ "&",/'; \
echo "};"; \
echo "#define AETHER_STDLIB_SYMBOL_COUNT \\"; \
echo " (sizeof(AETHER_STDLIB_SYMBOLS) / sizeof(AETHER_STDLIB_SYMBOLS[0]))"; \
echo "#endif"; \
} > "$$tmp"; \
if [ -f "$(STDLIB_SYMS_HEADER)" ] && cmp -s "$$tmp" "$(STDLIB_SYMS_HEADER)"; then \
rm -f "$$tmp"; \
else \
mv "$$tmp" "$(STDLIB_SYMS_HEADER)"; \
echo "Generated $(STDLIB_SYMS_HEADER)"; \
fi
.PHONY: gen-stdlib-symbols
gen-stdlib-symbols: $(STDLIB_SYMS_HEADER)
# #1746: the fingerprint of the front end, the code that turns module text
# into an AST (lexer, parser, AST, `when defined` answers, and the artifact
# codec itself). A compiled module artifact (.aea) records it and is only
# decoded by a compiler with the same one, so an edit to any of these files
# retires every artifact built before it. Over-invalidation (a comment edit)
# costs one re-parse; under-invalidation would load a stale AST, so the set
# errs wide. GENERATED, do not commit or edit by hand (gitignored).
FRONTEND_ID_HEADER := compiler/aether_frontend_id.h
FRONTEND_ID_SRC := compiler/ast.h compiler/ast.c compiler/parser/lexer.c \
compiler/parser/lexer.h compiler/parser/parser.c compiler/parser/parser.h \
compiler/parser/tokens.h compiler/aether_defines.c compiler/aether_aea.c \
compiler/aether_aea.h
$(FRONTEND_ID_HEADER): $(FRONTEND_ID_SRC) Makefile
@tmp="$(FRONTEND_ID_HEADER).tmp.$$$$"; \
id=$$(cat $(FRONTEND_ID_SRC) | cksum | awk '{print $$1 "-" $$2}'); \
{ \
echo "/* GENERATED by the Makefile from the front-end sources. Do not edit. */"; \
echo "#ifndef AETHER_FRONTEND_ID_H"; \
echo "#define AETHER_FRONTEND_ID_H"; \
echo "#define AETHER_FRONTEND_ID \"$$id\""; \
echo "#endif"; \
} > "$$tmp"; \
if [ -f "$(FRONTEND_ID_HEADER)" ] && cmp -s "$$tmp" "$(FRONTEND_ID_HEADER)"; then \
rm -f "$$tmp"; \
else \
mv "$$tmp" "$(FRONTEND_ID_HEADER)"; \
echo "Generated $(FRONTEND_ID_HEADER)"; \
fi
.PHONY: gen-frontend-id
gen-frontend-id: $(FRONTEND_ID_HEADER)
# #1746: compiled module artifacts for the std tree, into $(BUILD_DIR)/modules/ (the
# layout `make install` puts under $(PREFIX)/lib/aether/modules/). Only an
# installed toolchain reads artifacts; this target is for inspecting them.
.PHONY: modules
modules: compiler
@rm -rf $(BUILD_DIR)/modules
@sh scripts/build_module_artifacts.sh $(BUILD_DIR)/aetherc$(EXE_EXT) . $(BUILD_DIR)/modules
# Compiler configuration with ccache support.
# FreeBSD base has no gcc at all (clang is installed as `cc`).
ifdef WINDOWS_NATIVE
CC := gcc
else
BASE_CC := $(shell if command -v gcc >/dev/null 2>&1; then echo gcc; \
elif command -v cc >/dev/null 2>&1; then echo cc; \
else echo clang; fi)
CC := $(shell command -v ccache >/dev/null 2>&1 && echo "ccache $(BASE_CC)" || echo "$(BASE_CC)")
endif
EXTRA_CFLAGS ?=
PLATFORM ?= native
# Platform-specific overrides
ifeq ($(PLATFORM),wasm)
CC := emcc
EXTRA_CFLAGS += -DAETHER_NO_THREADING -DAETHER_NO_FILESYSTEM -DAETHER_NO_NETWORKING
SCHEDULER_SRC := runtime/scheduler/aether_scheduler_coop.c
else ifeq ($(PLATFORM),embedded)
EXTRA_CFLAGS += -DAETHER_NO_THREADING -DAETHER_NO_FILESYSTEM -DAETHER_NO_NETWORKING -DAETHER_NO_GETENV
SCHEDULER_SRC := runtime/scheduler/aether_scheduler_coop.c
else
# Auto-detect: if EXTRA_CFLAGS disables threading, use cooperative scheduler
ifneq ($(findstring AETHER_NO_THREADING,$(EXTRA_CFLAGS)),)
SCHEDULER_SRC := runtime/scheduler/aether_scheduler_coop.c
else
SCHEDULER_SRC := runtime/scheduler/multicore_scheduler.c
endif
endif
# ---------------------------------------------------------------------------
# FreeBSD cross-build (FREEBSD=1) — build the ae/aetherc toolchain FOR FreeBSD
# from a Linux (or any) host, via `zig cc`. GitHub Actions has no native
# FreeBSD runner, so the release FreeBSD zip is produced this way. This mirrors
# the link recipe tools/ae_cross.c uses for `ae build --target=x86_64-freebsd`,
# but applied to the compiler's own C sources through the Makefile's CC/LDFLAGS.
#
# Required inputs:
# ZIG path to a zig 0.16.0+ binary e.g. .../zig
# AETHER_SYSROOT FreeBSD base sysroot (headers + libc) e.g. .../bases/x86_64-freebsd15
# Provision Zig with this repo's scripts/get-zig.sh and the FreeBSD base with
# aether-crossbuild's fetch-freebsd-base.sh.
#
# Notes:
# - Zig 0.16 supplies the FreeBSD CRT/libc. The base sysroot supplies system
# headers and FreeBSD-only libraries such as Casper and libthr.
# - casper libs are ALWAYS needed: std/casper/aether_casper.c is
# unconditionally in the build and references cap_*; the base ships them.
# - Optional Tier-2 libs (OpenSSL/zlib/nghttp2/PCRE2/YAML) are forced OFF
# for the cross build: pkg-config on the host would find the host's Linux
# libs and poison the FreeBSD binary. A cross build ships those std
# features as their "unavailable" stubs (same as any build without them);
# a future crossbuild-sysroot-staged variant can re-enable them.
FREEBSD_CPU ?= x86_64
FREEBSD_ABI ?= 15.0
ifeq ($(FREEBSD),1)
ifeq ($(ZIG),)
$(error FREEBSD=1 needs ZIG=<path to zig> (>= 0.16.0))
endif
ifeq ($(AETHER_SYSROOT),)
$(error FREEBSD=1 needs AETHER_SYSROOT=<FreeBSD base sysroot>)
endif
# Zig 0.16 supplies the FreeBSD CRT/libc. State the ABI version explicitly so
# its startup objects match the FreeBSD 15 base used for the remaining system
# libraries. Target-root -L paths are resolved beneath --sysroot; passing the
# already-prefixed host paths makes Zig prefix the sysroot a second time.
CC := $(ZIG) cc -target $(FREEBSD_CPU)-freebsd.$(FREEBSD_ABI) --sysroot=$(AETHER_SYSROOT) -I$(AETHER_SYSROOT)/usr/include
# Host pkg-config is wrong for the target — force the optional libs off.
OPENSSL := 0
ZLIB := 0
NGHTTP2 := 0
PCRE2 := 0
YAML := 0
BROTLI := 0
ZSTD := 0
# Link the FreeBSD-only libraries from the base sysroot. Zig supplies the
# CRT and libc for this target; adding the base's startup objects as well
# would duplicate `_start`. Overrides the native LDFLAGS assembly below.
FREEBSD_LDFLAGS := -L/usr/lib -L/lib -lthr -lm \
-lcasper -lcap_pwd -lcap_sysctl -lcap_grp -lcap_dns
endif
# ---------------------------------------------------------------------------
# Windows cross-build (WINDOWS=1) — build the ae/aetherc toolchain FOR Windows
# from a Linux (or any) host, via `zig cc -target x86_64-windows-gnu` (#1592).
# Companion to FREEBSD=1 above, and deliberately simpler: zig BUNDLES the
# mingw-w64 headers and CRT sources and builds them on demand, so unlike the
# FreeBSD leg there is NO sysroot to fetch and no -nostdlib CRT bookkeeping.
#
# Required input:
# ZIG path to a zig 0.16.0+ binary e.g. .../zig
# Provision with this repo's scripts/get-zig.sh (same as FreeBSD CI).
#
# Purpose is a FAST Windows signal on a Linux runner (#1593): the MSYS2 legs
# take ~20 minutes, so Windows-specific compile breakage is discovered long
# after the author moved on. This lane compiles the same code
# paths in a few minutes. It is an EARLIER signal, never a replacement — the
# MSYS2 jobs stay the fidelity tip.
#
# Capability-lean by design: the optional Tier-2 libs are forced OFF, exactly
# as the FreeBSD cross does and for the same reason — host pkg-config would
# find the host's Linux libs and poison the Windows binary. Cross-compiling
# OpenSSL/nghttp2 for windows-gnu is the genuinely expensive part and is out
# of scope; std ships those features as their "unavailable" stubs. Vendored
# PCRE2 needs no host library, so std.regex stays available.
WINDOWS_CPU ?= x86_64
ifeq ($(WINDOWS),1)
ifeq ($(ZIG),)
$(error WINDOWS=1 needs ZIG=<path to zig> (>= 0.16.0))
endif
CC := $(ZIG) cc -target $(WINDOWS_CPU)-windows-gnu
AR := $(ZIG) ar
RANLIB := $(ZIG) ranlib
# Host pkg-config is wrong for the target — force the optional libs off.
OPENSSL := 0
ZLIB := 0
NGHTTP2 := 0
YAML := 0
# Same reason as the four above: the HOST's pkg-config would happily report
# a libbrotlienc/libzstd that the cross toolchain has no headers for, and
# the source then #includes a header zig cannot find. Both stub cleanly.
BROTLI := 0
ZSTD := 0
# PCRE2 has a vendored in-tree fallback (no host library involved), so
# std.regex survives the cross build.
PCRE2 := vendored
# NB __USE_MINGW_ANSI_STDIO=1 still applies (it keys off IS_WINDOWS
# below): zig ships mingw-w64's libmingwex, so the __mingw_* printf
# family resolves exactly as it does under MSYS2. Verified by a clean
# cross-build + link of compiler/ae/stdlib.
endif
# ---------------------------------------------------------------------------
# Android cross-build (ANDROID=1) — build the ae/aetherc toolchain FOR Android
# (bionic libc) from a Linux or macOS host, with the Android NDK's clang. Zig
# can name the target but ships no bionic, so the NDK is the toolchain here;
# GitHub's ubuntu runners carry one ($ANDROID_NDK_LATEST_HOME).
#
# Required input:
# ANDROID_NDK an Android NDK (r26+) e.g. .../android-ndk-r27c
# Optional:
# ANDROID_CPU aarch64 (default)
# ANDROID_API minimum API level, 29 (Android 10) by default
#
# Capability-lean like the other cross builds, for the same reason: the host's
# pkg-config would report the host's libraries. The optional Tier-2 libs are
# off (std ships those features as their "unavailable" stubs); PCRE2 is the
# vendored copy, so std.regex survives. The result is built, not tested: no
# Android runner runs the suite.
ANDROID_CPU ?= aarch64
ANDROID_API ?= 29
ifeq ($(ANDROID),1)
ifeq ($(ANDROID_NDK),)
$(error ANDROID=1 needs ANDROID_NDK=<path to an Android NDK>)
endif
ifeq ($(shell uname -s),Darwin)
ANDROID_NDK_HOST := darwin-x86_64
else
ANDROID_NDK_HOST := linux-x86_64
endif
ANDROID_NDK_BIN := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(ANDROID_NDK_HOST)/bin
CC := $(ANDROID_NDK_BIN)/$(ANDROID_CPU)-linux-android$(ANDROID_API)-clang
AR := $(ANDROID_NDK_BIN)/llvm-ar
RANLIB := $(ANDROID_NDK_BIN)/llvm-ranlib
OPENSSL := 0
ZLIB := 0
NGHTTP2 := 0
YAML := 0
BROTLI := 0
ZSTD := 0
PCRE2 := vendored
endif
# Objects and archives of different targets do not mix (a native link against
# PE objects dies deep in the link with confusing errors), so each target
# builds into its own tree, named by BUILD_TARGET_ID.
ifeq ($(WINDOWS),1)
BUILD_TARGET_ID := windows-$(WINDOWS_CPU)
else ifeq ($(ANDROID),1)
BUILD_TARGET_ID := android-$(ANDROID_CPU)
else ifeq ($(FREEBSD),1)
BUILD_TARGET_ID := freebsd-$(FREEBSD_CPU)
else
BUILD_TARGET_ID := native-$(DETECTED_OS)
endif
# #2321: the native build stays flat in build/, where every test, `ae` and the
# release packaging look for it; a cross build is quarantined under
# build/.alien/<target>/, so a warm native build and a warm cross build coexist
# and switching between them rebuilds nothing. Every path below goes through
# $(BUILD_DIR), which is why this sits ahead of all of them.
ifneq ($(filter native-%,$(BUILD_TARGET_ID)),)
BUILD_DIR := build
else
BUILD_DIR := build/.alien/$(BUILD_TARGET_ID)
endif
# Each tree records the target it holds. With one tree per target the guard
# below can only fire on a tree built before the split (a flat build/ that a
# cross build filled), which `make clean` resolves.
BUILD_TARGET_STAMP := $(BUILD_DIR)/.build-target
_STAMPED := $(strip $(shell cat $(BUILD_TARGET_STAMP) 2>/dev/null))
# The check runs at PARSE time, so it must not fire for the very goals that
# resolve it: `make clean` (and clean-ish goals) would otherwise be
# unreachable — the guard would block the only way out of the state it is
# complaining about. $(MAKECMDGOALS) is empty for a bare `make` (== all).
_CLEAN_ONLY := $(if $(strip $(filter-out clean distclean help,$(or $(MAKECMDGOALS),all))),,1)
ifneq ($(_STAMPED),)
ifneq ($(_STAMPED),$(BUILD_TARGET_ID))
ifneq ($(_CLEAN_ONLY),1)
$(error $(BUILD_DIR)/ holds $(_STAMPED) artifacts but this is a $(BUILD_TARGET_ID) build. \
It was filled before cross builds moved to build/.alien/<target>/ \
(#2321); run `make clean` once)
endif
endif
endif
# MinGW / MSYS2 (native Windows): bind the printf family to the
# C99-conformant __mingw_* implementations instead of legacy MSVCRT.
# MSVCRT mishandles the C99 conversions we emit (%lld / %llu / %zu / %g)
# and makes vsnprintf(NULL, 0, ...) return -1 instead of the would-be
# length — the latter breaks the two-pass sizing in the generated
# string-interpolation helper, yielding empty interpolated strings
# (#681). The generated `.c` also self-defines this before <stdio.h>;
# setting it here additionally covers the compiler, runtime, and stdlib
# translation units (which use snprintf/vsnprintf in os/json/etc.).
ifdef IS_WINDOWS
EXTRA_CFLAGS += -D__USE_MINGW_ANSI_STDIO=1
endif
# Windows application manifest (#2077): declares the UTF-8 process code page
# so the C runtime and the narrow Win32 calls read Aether's UTF-8 strings as
# UTF-8. Compiled with windres into one resource object that is linked into
# ae.exe, aetherc.exe and, by `ae build`/`ae run`, into every executable they
# produce (it is looked up beside libaether.a). Native MSYS2 builds only:
# the zig cross build has no windres, and its output carries no manifest.
MANIFEST_OBJ :=
ifdef IS_WINDOWS
ifneq ($(WINDOWS),1)
WINDRES ?= windres
MANIFEST_OBJ := $(BUILD_DIR)/aether_manifest.o
endif
endif
# Windows: the C entry point of an --emit=obj object
# (runtime/windows/aether_lib_main.c). Elsewhere the object carries a weak
# main(); a COFF weak definition does not reliably keep libmingw32's WinMain
# entry out of the link, so on Windows the entry is this one-member archive,
# which `ae cflags --libs` names ahead of -laether: linked only when the
# program has no main() of its own. Kept out of libaether.a because the shared
# runtime DLL is linked from that whole archive and must define no main.
LIB_MAIN_ARCHIVE :=
ifdef IS_WINDOWS
LIB_MAIN_ARCHIVE := $(BUILD_DIR)/libaether_main.a
endif
# Optional OpenSSL detection (enables HTTPS client). Probes pkg-config;
# falls back silently if OpenSSL isn't installed — the HTTP client still
# works for `http://` URLs and returns a clean error for `https://`.
# Override with OPENSSL=0 to force-disable.
OPENSSL ?= auto
ifeq ($(OPENSSL),auto)
OPENSSL_CFLAGS := $(shell pkg-config --cflags openssl 2>/dev/null)
OPENSSL_LDFLAGS := $(shell pkg-config --libs openssl 2>/dev/null)
else ifeq ($(OPENSSL),1)
OPENSSL_CFLAGS := $(shell pkg-config --cflags openssl 2>/dev/null)
OPENSSL_LDFLAGS := $(shell pkg-config --libs openssl 2>/dev/null)
else
OPENSSL_CFLAGS :=
OPENSSL_LDFLAGS :=
endif
ifneq ($(OPENSSL_LDFLAGS),)
OPENSSL_CFLAGS += -DAETHER_HAS_OPENSSL
endif
# zlib auto-detection: same pattern as OpenSSL. zlib is ambient on every
# POSIX box we care about; when absent (bare embedded, etc.) the stdlib
# wrappers report "zlib unavailable" cleanly.
# zlib auto-detection. pkg-config first, then an actual compile-and-link
# probe (#1690).
#
# The probe alone is not enough on macOS: the SDK ships libz and zlib.h
# but NO zlib.pc, so `pkg-config --libs zlib` fails while `-lz` links
# and runs. Detection therefore reported "no zlib", every gzip response
# came back uncompressed, and tests/integration/http_middleware_d2
# failed on that leg with no hint that a dependency was missing — the
# gzip middleware was behaving correctly for a build without a backend.
#
# The fallback compiles and links a one-liner against -lz rather than
# looking for a file, so it answers the question that actually matters:
# will this link. Kept second so a box where pkg-config DOES know about
# zlib (a non-default prefix, a cross sysroot) still gets its flags.
ZLIB ?= auto
ifeq ($(ZLIB),0)
ZLIB_CFLAGS :=
ZLIB_LDFLAGS :=
else
ZLIB_CFLAGS := $(shell pkg-config --cflags zlib 2>/dev/null)
ZLIB_LDFLAGS := $(shell pkg-config --libs zlib 2>/dev/null)
ifeq ($(ZLIB_LDFLAGS),)
ZLIB_LDFLAGS := $(shell sh tests/scripts/probe_zlib.sh "$(CC)" 2>/dev/null)
endif
endif
ifneq ($(ZLIB_LDFLAGS),)
ZLIB_CFLAGS += -DAETHER_HAS_ZLIB
endif
# nghttp2 auto-detection: enables HTTP/2 server-side handling
# (frame parsing, HPACK encode/decode, stream state machine, flow
# control) by wrapping libnghttp2. Same pkg-config probe shape as
# OpenSSL/zlib; when absent, http_server_set_h2 returns
# "HTTP/2 unavailable: built without libnghttp2" and HTTP/1.1
# remains the only protocol the server speaks. Issue #260 Tier 2.
NGHTTP2 ?= auto
ifeq ($(NGHTTP2),auto)
NGHTTP2_CFLAGS := $(shell pkg-config --cflags libnghttp2 2>/dev/null)
NGHTTP2_LDFLAGS := $(shell pkg-config --libs libnghttp2 2>/dev/null)
else ifeq ($(NGHTTP2),1)
NGHTTP2_CFLAGS := $(shell pkg-config --cflags libnghttp2 2>/dev/null)
NGHTTP2_LDFLAGS := $(shell pkg-config --libs libnghttp2 2>/dev/null)
else
NGHTTP2_CFLAGS :=
NGHTTP2_LDFLAGS :=
endif
ifneq ($(NGHTTP2_LDFLAGS),)
NGHTTP2_CFLAGS += -DAETHER_HAS_NGHTTP2
# #1896: nghttp2.h declares its API __declspec(dllimport) unless
# NGHTTP2_STATICLIB is defined (nghttp2.h:58-65). Without it every call
# compiles to an __imp_nghttp2_* reference, which the static libnghttp2.a
# the link actually uses cannot satisfy -- hence dozens of
# "undefined reference to `__imp_nghttp2_session_*'" on MinGW/MSYS2. The
# Windows link line is -static (WIN_LINK_LIBS), so the headers have to be
# told the same thing the linker was told.
ifneq ($(findstring MINGW,$(DETECTED_OS))$(findstring MSYS,$(DETECTED_OS))$(findstring CYGWIN,$(DETECTED_OS)),)
NGHTTP2_CFLAGS += -DNGHTTP2_STATICLIB
endif
endif
# Brotli detection: enables std.brotli (RFC 7932). `br` is what current
# browsers actually prefer over gzip in Accept-Encoding, so this is the
# higher-value codec for HTTP specifically. Same pkg-config probe shape as
# zlib/nghttp2; when absent, brotli.stream_new reports "brotli unavailable"
# and a server negotiating content-encoding falls back to gzip. Issue #1891.
BROTLI ?= auto
ifeq ($(BROTLI),0)
BROTLI_CFLAGS :=
BROTLI_LDFLAGS :=
else
BROTLI_CFLAGS := $(shell pkg-config --cflags libbrotlienc libbrotlidec 2>/dev/null)
# --static so libbrotlicommon comes with it. It is a Requires.private of
# libbrotlienc and libbrotlidec, so a plain --libs omits it -- fine when the
# loader resolves a shared object, fatal on a static link: MinGW's ld then
# reports a wall of undefined BrotliGetDictionary / BrotliDefaultAllocFunc /
# _kBrotli* references from inside libbrotlienc.a itself. Found on a real
# MSYS2 box; a Linux shared link hides it entirely. Asking for both
# libraries names -lbrotlicommon twice, and the link line must name each
# library once (optional_library_linking checks), so keep the LAST of each:
# a static link resolves left to right, and -lbrotlicommon has to follow
# libbrotlidec as well as libbrotlienc.
BROTLI_LDFLAGS := $(shell pkg-config --static --libs libbrotlienc libbrotlidec 2>/dev/null \
| awk '{ o = ""; for (i = NF; i > 0; i--) if (!seen[$$i]++) o = $$i " " o; print o }')
endif
ifneq ($(BROTLI_LDFLAGS),)
BROTLI_CFLAGS += -DAETHER_HAS_BROTLI
endif
# zstd detection: enables std.zstd (RFC 8878). A different FORMAT from
# DEFLATE, not a faster zlib -- its case is strongest away from the browser
# (archives, logs, snapshots, internal RPC), since Content-Encoding: zstd
# support is thinner than br/gzip. Same pkg-config probe shape as the others;
# when absent, zstd.stream_new reports "zstd unavailable". Issue #1891.
ZSTD ?= auto
ifeq ($(ZSTD),0)
ZSTD_CFLAGS :=
ZSTD_LDFLAGS :=
else
ZSTD_CFLAGS := $(shell pkg-config --cflags libzstd 2>/dev/null)
ZSTD_LDFLAGS := $(shell pkg-config --libs libzstd 2>/dev/null)
endif
ifneq ($(ZSTD_LDFLAGS),)
ZSTD_CFLAGS += -DAETHER_HAS_ZSTD
endif
# PCRE2 detection: enables std.regex (full Perl-compatible regex —
# captures, $-substitutions, Unicode, look-around, etc.). A system
# libpcre2-8 found by pkg-config is preferred (distro security updates
# keep covering it); without one the build compiles the vendored copy in
# std/regex/pcre2/ instead (#1389), so std.regex works everywhere rather
# than silently degrading to its "built without libpcre2-8" stub — the
# failure mode that cost aether-ui 8 rounds of misdiagnosis. PCRE2=0
# opts out entirely (stub mode, for minimal builds); PCRE2=vendored
# forces the vendored engine even when a system library exists.
PCRE2 ?= auto
ifeq ($(PCRE2),auto)
PCRE2_CFLAGS := $(shell pkg-config --cflags libpcre2-8 2>/dev/null)
PCRE2_LDFLAGS := $(shell pkg-config --libs libpcre2-8 2>/dev/null)
else ifeq ($(PCRE2),1)
PCRE2_CFLAGS := $(shell pkg-config --cflags libpcre2-8 2>/dev/null)
PCRE2_LDFLAGS := $(shell pkg-config --libs libpcre2-8 2>/dev/null)
else
# PCRE2=0 — regex deliberately disabled. std.regex now REFUSES to compile its
# no-op stubs unless asked by name, so that a build which merely forgot the
# defines fails loudly instead of stubbing every regex call at runtime. This
# is the one build that genuinely wants them, so it opts in explicitly.
PCRE2_CFLAGS := -DAETHER_REGEX_ALLOW_STUB
PCRE2_LDFLAGS :=
endif
ifneq ($(PCRE2_LDFLAGS),)
PCRE2_CFLAGS += -DAETHER_HAS_PCRE2
# On Windows/MinGW we link the STATIC libpcre2-8.a; without PCRE2_STATIC the
# pcre2.h prototypes default to __declspec(dllimport), so the linker looks for
# __imp_pcre2_* import stubs that the static lib doesn't provide. Define it so
# the header declares plain symbols matching the static archive.
ifneq ($(IS_WINDOWS),)
PCRE2_CFLAGS += -DPCRE2_STATIC
endif
else ifneq ($(PCRE2),0)
# No system libpcre2-8 (or PCRE2=vendored): build the vendored engine.
# AETHER_VENDOR_PCRE2 turns std/regex/aether_pcre2_vendored.c from an
# empty TU into the full unity build, and switches aether_regex.c to
# the in-tree pcre2.h. PCRE2_LDFLAGS stays empty on purpose — the
# engine is inside libaether.a, so no -l flag may be emitted anywhere
# (AETHER_PCRE2_LIBS's emptiness is load-bearing, as with every other
# capability macro).
PCRE2_CFLAGS := -DAETHER_HAS_PCRE2 -DAETHER_VENDOR_PCRE2
endif
# YAML auto-detection: enables std.yaml via libfyaml.
# YAML=auto (default) libfyaml if pkg-config finds it, else the stubs that
# report "std.yaml unavailable" at run time;
# YAML=1 libfyaml REQUIRED: the build stops here without it.
# The release builds pass this, so a release can no
# longer ship the stubs unnoticed (0.889.0 did, on
# macOS and Linux alike: auto found nothing on a
# runner without libfyaml and said nothing);
# YAML=0 the stubs, deliberately.
YAML ?= auto
ifeq ($(YAML),auto)
YAML_CFLAGS := $(shell pkg-config --cflags libfyaml 2>/dev/null)
YAML_LDFLAGS := $(shell pkg-config --libs libfyaml 2>/dev/null)
else ifeq ($(YAML),1)
YAML_CFLAGS := $(shell pkg-config --cflags libfyaml 2>/dev/null)
YAML_LDFLAGS := $(shell pkg-config --libs libfyaml 2>/dev/null)
ifeq ($(YAML_LDFLAGS),)
$(error YAML=1 but pkg-config finds no libfyaml: install it (apt libfyaml-dev, brew libfyaml, pkg libfyaml, MSYS2 mingw-w64-x86_64-libfyaml) or build with YAML=auto / YAML=0)
endif
else
YAML_CFLAGS :=
YAML_LDFLAGS :=
endif
ifneq ($(YAML_LDFLAGS),)
# libfyaml found (auto/1): compile the real bindings + link it.
YAML_CFLAGS += -DAETHER_HAS_YAML
# #1896: on MinGW/MSYS2 the link line carries -static (WIN_LINK_LIBS below),
# which avoids libwinpthread/libgcc DLL dependencies in release binaries.
# -static is NOT positional for library resolution -- it governs the whole
# link -- so ld demands libfyaml.a. MSYS2's mingw-w64-x86_64-libfyaml ships
# ONLY libfyaml.dll.a, so the link fails with "cannot find -lfyaml" even
# though the library is installed and pkg-config reports it correctly.
# fyaml is the only one of our dependencies with no static archive there
# (openssl, zlib, nghttp2, pcre2, brotli and zstd all have both), so scope
# the exception to it rather than dropping -static and giving every Windows
# binary a DLL tail.
ifneq ($(findstring MINGW,$(DETECTED_OS))$(findstring MSYS,$(DETECTED_OS))$(findstring CYGWIN,$(DETECTED_OS)),)
YAML_LDFLAGS := -Wl,-Bdynamic $(YAML_LDFLAGS) -Wl,-Bstatic
endif
else
# libfyaml absent (or YAML=0): define NOTHING and link NOTHING, so
# aether_yaml.c compiles its unavailable-stub path and the build works
# everywhere. (The old `else` forced -lfyaml + -DAETHER_HAS_YAML here,
# which broke every build without libfyaml — the whole stdlib failed on
# `#include <libfyaml.h>: No such file`.)
YAML_CFLAGS :=
YAML_LDFLAGS :=
endif
# #959: homebrew's pkg-config .pc files emit versioned
# `-L/opt/homebrew/Cellar/<pkg>/<version>/lib` paths. Those `-L` dirs get
# baked into the shipped `ae` binary (via -DAETHER_*_LIBS below) and break
# every user `ae build` with `ld: library 'ssl' not found` the moment the
# formula is upgraded to a new version. Rewrite each Cellar path to the
# version-agnostic `/opt/homebrew/opt/<pkg>` symlink homebrew always keeps
# pointed at the current version. A no-op on layouts without `/Cellar/`
# (Linux, MacPorts, custom prefixes), so it applies unconditionally. The
# CFLAGS keep the as-probed paths — they are only used to build Aether
# itself (where the probed version is present), never baked into user builds.
cellar_to_opt = $(shell printf '%s' '$(1)' | sed 's|/Cellar/\([^/]*\)/[^/]*|/opt/\1|g')
OPENSSL_LDFLAGS := $(call cellar_to_opt,$(OPENSSL_LDFLAGS))
ZLIB_LDFLAGS := $(call cellar_to_opt,$(ZLIB_LDFLAGS))
NGHTTP2_LDFLAGS := $(call cellar_to_opt,$(NGHTTP2_LDFLAGS))
PCRE2_LDFLAGS := $(call cellar_to_opt,$(PCRE2_LDFLAGS))
BROTLI_LDFLAGS := $(call cellar_to_opt,$(BROTLI_LDFLAGS))
ZSTD_LDFLAGS := $(call cellar_to_opt,$(ZSTD_LDFLAGS))
YAML_LDFLAGS := $(call cellar_to_opt,$(YAML_LDFLAGS))
# -fPIC on every runtime/stdlib object so the precompiled `build/libaether.a`
# can be linked into a shared object by `ae build --emit=lib`. Without it,
# any `--emit=lib` build that pulls in a TLS-using runtime object (e.g.
# std.http's `aether_http.o`, whose initial-exec TLS emits an
# R_X86_64_TPOFF32 relocation illegal in a `.so`) fails to link
# (vcr_embed_abi_wish.md Part A). Negligible codegen cost on x86-64/arm64
# (most distros already default to PIE); one archive serves both the exe
# link and the shared-object link.
# Two kinds of flag, kept apart on purpose.
#
# AETHER_REQUIRED_CFLAGS is structural: the include paths, dependency
# generation, and the defines that select which code is compiled in. The build
# is not merely slower without them, it is a different build.
#
# CFLAGS is the tunable half: optimisation, debug info, warnings, sanitizers.
# Callers replace it wholesale and are right to, e.g.
#
# make test-build CFLAGS="-O0 -g" (valgrind leg)
# make compiler CFLAGS="-fsanitize=address ... -O1 -g" (ASan leg)
#
# They were previously one variable, so those commands did far more than they
# said: every -I disappeared, and with them -DAETHER_HAS_SANDBOX, which gates
# real code in runtime/aether_sandbox.c. The sanitizer and valgrind legs were
# therefore testing a configuration that never ships. It stayed invisible
# because the remaining includes all resolved same-directory or relatively,
# until a header outside those trees (include/libaether.h, #1420) finally made
# it fail out loud.
#
# Recipes use `$(AETHER_REQUIRED_CFLAGS) $(CFLAGS)` so tuning one cannot
# silently delete the other.
AETHER_REQUIRED_CFLAGS = -fPIC -Iinclude -Icompiler -Iruntime -Iruntime/actors -Iruntime/scheduler -Iruntime/utils -Iruntime/memory -Iruntime/config -Istd -Istd/string -Istd/io -Istd/math -Istd/net -Istd/collections -Istd/json -Istd/yaml -MMD -MP -DAETHER_VERSION=\"$(VERSION)\" -DAETHER_HAS_SANDBOX $(OPENSSL_CFLAGS) $(ZLIB_CFLAGS) $(NGHTTP2_CFLAGS) $(PCRE2_CFLAGS) $(YAML_CFLAGS) $(BROTLI_CFLAGS) $(ZSTD_CFLAGS)
CFLAGS = -O2 -Wall -Wextra -Wstrict-prototypes -Wno-unused-parameter -Wno-unused-function $(EXTRA_CFLAGS)
# Casper link libraries (FreeBSD only) — std.casper delegates DNS /
# passwd / sysctl past Capsicum capability mode. libcasper + the
# per-service libs ship in the FreeBSD base system. We resolve them by
# globbing the actual shared-object filenames rather than relying on
# `-lcasper` (the `.so` linker symlinks are absent on some installs,
# e.g. GhostBSD) or hardcoding a soname version (libcap_sysctl is at
# .so.2 while its siblings are at .so.1). Empty if the libs aren't
# present — std.casper then links its non-FreeBSD stub path.
CASPER_LDFLAGS :=
ifeq ($(shell uname -s),FreeBSD)
CASPER_LDFLAGS := $(firstword $(wildcard /lib/libcasper.so.* /usr/lib/libcasper.so.*)) \
$(firstword $(wildcard /lib/libcap_net.so.* /usr/lib/libcap_net.so.*)) \
$(firstword $(wildcard /lib/libcap_pwd.so.* /usr/lib/libcap_pwd.so.*)) \
$(firstword $(wildcard /lib/libcap_sysctl.so.* /usr/lib/libcap_sysctl.so.*))
endif
# std.audio's vendored miniaudio backend (std/audio/miniaudio.h). miniaudio
# dlopens the actual OS audio backends (ALSA/PulseAudio/sndio) at runtime, so
# only the loader/threads/math libs are needed at link time; macOS needs the
# audio frameworks. Windows folds its audio into the existing WIN_LINK_LIBs.
AUDIO_LDFLAGS :=
# Keyed on the HOST uname, so a cross build must opt out explicitly or it
# picks up the host's POSIX libs (zig-lld: "unable to find dynamic system
# library 'pthread'"). Windows folds audio into WIN_LINK_LIBS either way.
ifeq ($(WINDOWS),1)
AUDIO_LDFLAGS :=
else ifeq ($(ANDROID),1)
# bionic has threads in libc (no separate libpthread to link).
AUDIO_LDFLAGS := -ldl -lm
else ifeq ($(shell uname -s),Linux)
AUDIO_LDFLAGS := -lpthread -ldl -lm
else ifeq ($(shell uname -s),FreeBSD)
AUDIO_LDFLAGS := -lpthread -lm
else ifeq ($(shell uname -s),Darwin)
AUDIO_LDFLAGS := -framework CoreFoundation -framework CoreAudio -framework AudioToolbox
endif
# The link-side half of the AETHER_REQUIRED_CFLAGS split above, and it has to
# stay in lockstep with it: feature detection decides BOTH the -I/-D for a
# dependency and the -l that resolves the symbols that then get emitted. Split
# only the compile side and an `LDFLAGS="..."` override compiles OpenSSL in and
# links it out, so the build fails on undefined SSL symbols. Neither half is
# tunable on its own.
ifeq ($(FREEBSD),1)
# Cross build: the explicit base-libc link tail replaces the native assembly
# (which would pull the host's -lm / -pthread / globbed host casper libs).
AETHER_REQUIRED_LDFLAGS = $(FREEBSD_LDFLAGS)
else
AETHER_REQUIRED_LDFLAGS = -lm $(OPENSSL_LDFLAGS) $(ZLIB_LDFLAGS) $(NGHTTP2_LDFLAGS) $(PCRE2_LDFLAGS) $(YAML_LDFLAGS) $(CASPER_LDFLAGS) $(AUDIO_LDFLAGS) $(BROTLI_LDFLAGS) $(ZSTD_LDFLAGS)
endif
# The toolchain's own binaries (aetherc, ae, aether-lsp, apkg) link without
# libfyaml. None of them parses YAML: aetherc leaves std.yaml's object out of
# its link (below), and the others take std from libaether.a, which pulls it
# in only for a caller. Linked with it, a release built against libfyaml
# could not even start on a machine without the library. A program that
# imports std.yaml links it, through AETHER_YAML_LIBS, and only that program.
AETHER_TOOL_LDFLAGS = $(filter-out $(YAML_LDFLAGS),$(AETHER_REQUIRED_LDFLAGS))
AETHERC_STD_OBJS = $(filter-out $(OBJ_DIR)/std/yaml/aether_yaml.o,$(STD_OBJS))
# Tunable link flags: sanitizers and anything else a caller passes. Empty by
# default, so `make ... LDFLAGS="-fsanitize=address"` adds to the required set
# rather than replacing it.
LDFLAGS =
# Hardening flags (issue #396). Opt-in via `HARDEN=1`. The CI matrix
# pins a Linux/gcc + HARDEN=1 entry so a hardened-build regression
# trips a red check before merge; the default release CFLAGS stay
# unchanged because (a) some hardening flags add measurable runtime
# overhead and (b) macOS Clang has a documented gap with
# `_FORTIFY_SOURCE` on a few historical setups. Each flag below is
# justified inline:
#
# -fstack-protector-all : stack canaries on every function — not
# just those gcc heuristics flag (-strong)
# — catches the smashing class of bugs
# that escape the default heuristic.
# -D_FORTIFY_SOURCE=2 : runtime checks on read/write/memcpy/
# strncpy/printf-family wrappers. Requires
# at least -O1; we already use -O2, so
# nothing extra needed. Linux/gcc will
# warn at compile time when it can prove
# a buffer overflow; turn those into
# real bug fixes, not blanket suppressions.
# -Wformat -Wformat-security :
# flag printf-family format strings that
# come from non-literal sources (the
# classic %s-format-injection pattern).
# Default in modern Linux distros; we
# standardise on it explicitly.
ifeq ($(HARDEN),1)
CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security
endif
ifneq ($(FREEBSD),1)
ifneq ($(WINDOWS),1)
ifneq ($(PLATFORM),wasm)
ifneq ($(PLATFORM),embedded)
ifeq ($(findstring AETHER_NO_THREADING,$(EXTRA_CFLAGS)),)
AETHER_REQUIRED_LDFLAGS += -pthread
endif
endif
endif
endif
endif
# Zero warnings achieved - ready for -Werror
OBJ_DIR = $(BUILD_DIR)/obj
# Windows-specific: -static avoids libwinpthread/libgcc DLL dependencies.
# MinGW OpenSSL 3 static libs pull in Windows Crypto API + GDI/Advapi
# symbols (CertFreeCertificateContext, CertOpenSystemStoreW, etc.) that
# aren't auto-linked — we add the import libs explicitly so std.net TLS
# and std.cryptography work in Windows release binaries.
WIN_LINK_LIBS = -static -lws2_32 -lcrypt32 -lgdi32 -luser32 -ladvapi32 -lbcrypt -ldbghelp
# The compiler's stack is its own on every platform: aetherc and aether-lsp
# run on a thread whose stack the compiler sizes (compiler/
# aether_compile_stack.h, #3539), not on the main thread's, which on Windows
# was 2 MB and overflowed at about 600 chained operators (#2907).
ifdef WINDOWS_NATIVE
AETHER_REQUIRED_LDFLAGS += $(WIN_LINK_LIBS)
else ifneq ($(findstring MINGW,$(DETECTED_OS)),)
AETHER_REQUIRED_LDFLAGS += $(WIN_LINK_LIBS)
else ifneq ($(findstring MSYS,$(DETECTED_OS)),)
AETHER_REQUIRED_LDFLAGS += $(WIN_LINK_LIBS)
else ifneq ($(findstring CYGWIN,$(DETECTED_OS)),)
AETHER_REQUIRED_LDFLAGS += $(WIN_LINK_LIBS)
endif
COMPILER_SRC = compiler/aetherc.c compiler/parser/lexer.c compiler/parser/parser.c compiler/ast.c compiler/analysis/typechecker.c compiler/analysis/contract_eval.c compiler/analysis/derive.c compiler/analysis/actor_reply.c compiler/analysis/missing_return.c compiler/analysis/actor_protocol.c compiler/analysis/free_check.c compiler/analysis/slice_escape.c compiler/analysis/slice_own.c compiler/analysis/hoist.c compiler/analysis/slice_coerce.c compiler/analysis/sandbox_trust.c compiler/analysis/quote.c compiler/aether_defines.c compiler/codegen/codegen.c compiler/codegen/codegen_expr.c compiler/codegen/codegen_stmt.c compiler/codegen/codegen_actor.c compiler/codegen/codegen_func.c compiler/codegen/codegen_schema.c compiler/codegen/codegen_own.c compiler/aether_error.c compiler/aether_module.c compiler/aether_aea.c compiler/analysis/type_inference.c compiler/codegen/optimizer.c compiler/aether_diagnostics.c compiler/aether_strmap.c compiler/aether_compile_stack.c runtime/actors/aether_message_registry.c lsp/aether_lsp.c
COMPILER_LIB_SRC = compiler/parser/lexer.c compiler/parser/parser.c compiler/ast.c compiler/analysis/typechecker.c compiler/analysis/contract_eval.c compiler/analysis/derive.c compiler/analysis/actor_reply.c compiler/analysis/missing_return.c compiler/analysis/actor_protocol.c compiler/analysis/free_check.c compiler/analysis/slice_escape.c compiler/analysis/slice_own.c compiler/analysis/hoist.c compiler/analysis/slice_coerce.c compiler/analysis/sandbox_trust.c compiler/analysis/quote.c compiler/aether_defines.c compiler/codegen/codegen.c compiler/codegen/codegen_expr.c compiler/codegen/codegen_stmt.c compiler/codegen/codegen_actor.c compiler/codegen/codegen_func.c compiler/codegen/codegen_schema.c compiler/codegen/codegen_own.c compiler/aether_error.c compiler/aether_module.c compiler/aether_aea.c compiler/analysis/type_inference.c compiler/codegen/optimizer.c compiler/aether_diagnostics.c compiler/aether_strmap.c compiler/aether_compile_stack.c runtime/actors/aether_message_registry.c lsp/aether_lsp.c
RUNTIME_SRC = $(SCHEDULER_SRC) runtime/scheduler/scheduler_optimizations.c runtime/scheduler/aether_io_poller_epoll.c runtime/scheduler/aether_io_poller_kqueue.c runtime/scheduler/aether_io_poller_poll.c runtime/config/aether_optimization_config.c runtime/memory/aether_arena.c runtime/memory/aether_pool.c runtime/memory/aether_memory_stats.c runtime/utils/aether_trace.c runtime/utils/aether_spawn.c runtime/utils/aether_bounds_check.c runtime/utils/aether_test.c runtime/memory/aether_arena_optimized.c runtime/aether_runtime_types.c runtime/aether_locale_num.c runtime/utils/aether_cpu_detect.c runtime/utils/aether_simd_vectorized.c runtime/aether_runtime.c runtime/aether_numa.c runtime/aether_sandbox.c runtime/sandbox/spawn_sandboxed_linux.c runtime/sandbox/aether_sandbox_kernel_linux.c runtime/sandbox/spawn_sandboxed_bsd.c runtime/sandbox/spawn_sandboxed_stub.c runtime/sandbox/capsicum_autosandbox.c runtime/sandbox/aether_audit.c runtime/aether_shared_map.c runtime/aether_observe.c runtime/aether_schema.c runtime/aether_process_mem.c runtime/aether_host.c runtime/aether_exec.c runtime/aether_resource_caps.c runtime/libaether_caps.c runtime/actors/aether_send_buffer.c runtime/actors/aether_send_message.c runtime/actors/aether_actor_thread.c runtime/actors/aether_panic.c runtime/actors/aether_unwind.c runtime/actors/aether_supervision.c runtime/aether_callbacks.c
STD_SRC = std/string/aether_string.c std/math/aether_math.c std/net/aether_http.c std/net/aether_tls_trust.c std/net/aether_http_server.c std/net/aether_http_pool.c std/net/aether_http_park.c std/net/aether_http_evloop.c std/net/aether_net.c std/udp/aether_udp.c std/collections/aether_collections.c std/intmap/aether_intmap.c std/json/aether_json.c std/yaml/aether_yaml.c std/xml/aether_xml.c std/fs/aether_fs.c std/log/aether_log.c std/io/aether_io.c std/os/aether_os.c std/ipc/aether_ipc.c std/mem/aether_mem.c std/cryptography/aether_cryptography.c std/cryptography/aes/aether_aes.c std/zlib/aether_zlib.c std/brotli/aether_brotli.c std/zstd/aether_zstd.c std/lzf/lzf_c.c std/lzf/lzf_d.c std/lzf/aether_lzf.c std/dl/aether_dl.c std/http/middleware/aether_middleware.c std/http/server/h2/aether_h2.c std/http/proxy/aether_proxy_pool.c std/http/proxy/aether_proxy_lb.c std/http/proxy/aether_proxy_breaker.c std/http/proxy/aether_proxy_health.c std/http/proxy/aether_proxy_cache.c std/http/proxy/aether_proxy_opts.c std/http/proxy/aether_proxy_metrics.c std/http/proxy/aether_proxy_middleware.c std/http/script_gateway/aether_script_gateway.c std/bytes/aether_bytes.c std/bytes/cursor/aether_bytes_cursor.c std/strbuilder/aether_strbuilder.c std/config/aether_config.c std/actors/aether_actor_registry.c std/regex/aether_regex.c std/regex/aether_pcre2_vendored.c std/capsicum/aether_capsicum.c std/casper/aether_casper.c std/snapshot/aether_snapshot.c std/sync/aether_sync.c std/audio/aether_audio.c std/worker/aether_worker.c std/alloc/aether_alloc.c std/tracking/aether_tracking.c std/tar/aether_tar.c std/quote/aether_quote.c std/unicode/aether_unicode.c std/unicode/utf8proc/utf8proc.c
# Stdlib sources that reference scheduler internals (scheduler_io_register,
# g_sync_step_actor, current_core_id). Excluded from the compiler binary
# because aetherc does not link the runtime scheduler, but included in
# libaether.a and user programs where the runtime is present.
STD_REACTOR_SRC = std/net/aether_actor_bridge.c
COLLECTIONS_SRC = std/collections/aether_set.c std/collections/aether_pqueue.c std/collections/aether_intarr.c std/collections/aether_floatarr.c std/collections/aether_longarr.c std/collections/aether_strarr.c std/collections/aether_bits.c std/collections/aether_stringlist.c std/collections/aether_stringseq.c
# I/O poller backends (needed by both compiler and runtime targets)
IO_POLLER_SRC = runtime/scheduler/aether_io_poller_epoll.c runtime/scheduler/aether_io_poller_kqueue.c runtime/scheduler/aether_io_poller_poll.c
# Object files
COMPILER_OBJS = $(COMPILER_SRC:%.c=$(OBJ_DIR)/%.o)
# Tools driver, per-object build (#1221). ae.c was one 8.5k-line TU compiled
# in a single gcc invocation, so any edit recompiled the whole driver. Each
# ae_*.c now compiles to its own object with -MMD -MP dependency tracking, so
# an edit recompiles only the touched object (and, via the .d files, whatever
# includes a changed ae_internal.h) and relinks. The _LIBS macros are baked in
# so build_gcc_cmd can construct user-program link lines.
TOOLS_CFLAGS = -O2 -Itools -MMD -MP \
-DAETHER_VERSION=\"$(VERSION)\" \
-DAETHER_OPENSSL_LIBS='"$(OPENSSL_LDFLAGS)"' \
-DAETHER_ZLIB_LIBS='"$(ZLIB_LDFLAGS)"' \
-DAETHER_NGHTTP2_LIBS='"$(NGHTTP2_LDFLAGS)"' \
-DAETHER_PCRE2_LIBS='"$(PCRE2_LDFLAGS)"' \
-DAETHER_YAML_LIBS='"$(YAML_LDFLAGS)"' \
-DAETHER_CASPER_LIBS='"$(CASPER_LDFLAGS)"' \
-DAETHER_AUDIO_LIBS='"$(AUDIO_LDFLAGS)"' \
-DAETHER_BROTLI_LIBS='"$(BROTLI_LDFLAGS)"' \
-DAETHER_ZSTD_LIBS='"$(ZSTD_LDFLAGS)"' \
$(if $(AETHER_ENABLE_LLM),-DAETHER_ENABLE_LLM=1)
TOOLS_SRC = tools/ae.c tools/ae_help.c tools/ae_fmt.c tools/ae_bindgen.c \
tools/ae_cross.c tools/ae_repl.c tools/ae_version.c \
tools/ae_cache.c tools/ae_checksec.c tools/ae_sha256.c tools/apkg/toml_parser.c $(if $(AETHER_ENABLE_LLM),tools/llm_shim.c)
TOOLS_OBJS = $(TOOLS_SRC:%.c=$(OBJ_DIR)/%.o)
COMPILER_LIB_OBJS = $(COMPILER_LIB_SRC:%.c=$(OBJ_DIR)/%.o)
RUNTIME_OBJS = $(RUNTIME_SRC:%.c=$(OBJ_DIR)/%.o)
IO_POLLER_OBJS = $(IO_POLLER_SRC:%.c=$(OBJ_DIR)/%.o)
STD_OBJS = $(STD_SRC:%.c=$(OBJ_DIR)/%.o)
STD_REACTOR_OBJS = $(STD_REACTOR_SRC:%.c=$(OBJ_DIR)/%.o)
COLLECTIONS_OBJS = $(COLLECTIONS_SRC:%.c=$(OBJ_DIR)/%.o)
TEST_OBJS = $(TEST_SRC:%.c=$(OBJ_DIR)/%.o)
# The version is injected as -DAETHER_VERSION on the command line, NOT via an
# #include, so -MMD cannot see it: bump VERSION and every object still looks up
# to date, leaving a binary that reports the PREVIOUS version. Measured: a tree
# whose generated header said 0.516.0 produced an `ae --version` of 0.515.0,
# because build/obj/tools/ae_version.o was hours older than the header and
# nothing told make to rebuild it.
#
# $(VERSION_HEADER) is regenerated whenever VERSION changes (rule above), so
# making the version-bearing objects depend on it is the cheap, honest link
# between "the version changed" and "recompile the things that bake it in".
$(OBJ_DIR)/tools/ae_version.o: $(VERSION_HEADER)
$(OBJ_DIR)/tools/ae.o: $(VERSION_HEADER)
$(OBJ_DIR)/compiler/aetherc.o: $(VERSION_HEADER)
# aether_abi.h includes it (#2957): the runtime ABI id the runtime reports
# and the one the code generator writes into each library.
$(OBJ_DIR)/runtime/aether_exec.o: $(VERSION_HEADER)
$(OBJ_DIR)/compiler/codegen/codegen.o: $(VERSION_HEADER)
# Test files using TEST() macro system (exclude standalone tests)
TEST_SRC = tests/runtime/test_harness.c \
tests/runtime/test_main.c \
tests/runtime/test_64bit.c \
tests/runtime/test_runtime_collections.c \
tests/runtime/test_runtime_strings.c \
tests/runtime/test_runtime_panic.c \
tests/runtime/test_runtime_strbuilder.c \
tests/runtime/test_runtime_math.c \
tests/runtime/test_runtime_json.c \
tests/runtime/test_runtime_http.c \
tests/runtime/test_runtime_net.c \
tests/runtime/test_runtime_udp.c \
tests/runtime/test_io_poller.c \
tests/runtime/test_runtime_observe.c \
tests/runtime/test_runtime_process_mem.c \
tests/runtime/test_spawn_env.c \
tests/runtime/test_ipc_write.c \
tests/runtime/test_runtime_schema.c \
tests/runtime/test_runtime_intmap.c \
tests/runtime/test_runtime_shared_map.c \
tests/runtime/test_tools_sha256.c tools/ae_sha256.c \
tests/runtime/test_scheduler.c \
tests/runtime/test_scheduler_stress.c \
tests/runtime/test_zerocopy.c \
tests/runtime/test_lockfree_mailbox.c \
tests/runtime/test_send_buffer.c \
tests/runtime/test_scheduler_optimizations.c \
tests/runtime/test_spsc_queue.c \
tests/runtime/test_worksteal_race.c \
tests/runtime/test_ask_relief.c \
tests/runtime/test_ask_message.c \
tests/runtime/test_wait_inside_actor.c \
tests/runtime/test_step_context.c \
tests/runtime/test_exec_accounts.c \
tests/runtime/test_abi_layout.c \
tests/runtime/test_sender_move_order.c \
tests/runtime/test_actor_thread_sends.c \
tests/runtime/test_overflow_reentry.c \
tests/runtime/test_actor_death.c \
tests/runtime/test_actor_rebirth.c \
tests/runtime/test_mailbox_bound.c \