-
Notifications
You must be signed in to change notification settings - Fork 139
Expand file tree
/
Copy pathMakefile
More file actions
3827 lines (3654 loc) · 285 KB
/
Copy pathMakefile
File metadata and controls
3827 lines (3654 loc) · 285 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
# Spinel AOT Compiler - Makefile
#
# Usage:
# make Build the C compiler (runtime + spinel + tools)
# make test Run the feature tests (always a fresh run)
# make bench Run benchmarks vs CRuby
# make bench-compile Time analysis/emission on a synthetic program at K=100, 200
# make optcarrot End-to-end optcarrot integration test
# make check Fast pre-commit: rebuild + tests
# make gate Full pre-push: test || bench || optcarrot (reuses the
# passes of unchanged programs, see RUN_ONE_TEST)
# make gate-full The same with GATE_CACHE=0: every program built and run
# make clean Remove built binaries
# COPT: optimization level override. Default -O2 for release builds.
# Set `COPT=-O0 -g0` in config.mk for fast iteration / debugging.
# A command-line `make COPT=-O0` takes precedence.
COPT ?= -O2
# PORTABLE=1 compiles the runtime and the test programs with -DSP_PORTABLE:
# every fallback in lib/sp_compat.h, even where the GNU extension exists, so a
# GCC or clang tree runs the code a plain C compiler would get. Use a tree of
# its own (the objects differ).
ifeq ($(PORTABLE),1)
COPT += -DSP_PORTABLE
endif
# Machine-local overrides (gitignored config.mk is a common dev pattern).
-include config.mk
# Shared toolchain configuration (CC wrapping, CFLAGS, stamps, …).
include common.mk
# Prism library: prefer vendor/prism (fetched via `make deps`), then fall
# back to the Prism gem if one is installed. Override with PRISM_DIR=…
PRISM_VERSION ?= 1.9.0
ifneq ($(wildcard vendor/prism/include/prism.h),)
PRISM_DIR ?= vendor/prism
else
PRISM_DIR ?= $(shell ruby -rprism -e 'puts $$LOADED_FEATURES.grep(/prism/).first.sub(%r{/lib/.*}, "")' 2>/dev/null)
endif
PRISM_INC = $(PRISM_DIR)/include
PRISM_SRC = $(wildcard $(PRISM_DIR)/src/*.c) $(wildcard $(PRISM_DIR)/src/util/*.c)
PRISM_OBJ = $(patsubst $(PRISM_DIR)/src/%.c,build/prism/%.o,$(PRISM_SRC))
PRISM_LIB = build/libprism.a
# rbs C parser. Fetched via `make deps` from rubygems.org. Consumed by
# spinel_rbs_extract to produce a seed file for the analyzer.
RBS_VERSION ?= 4.0.1
RBS_DIR = vendor/rbs
RBS_INC = $(RBS_DIR)/include
RBS_SRC = $(wildcard $(RBS_DIR)/src/*.c) $(wildcard $(RBS_DIR)/src/util/*.c)
RBS_OBJ = $(patsubst $(RBS_DIR)/src/%.c,build/rbs/%.o,$(RBS_SRC))
RBS_LIB = build/librbs.a
.PHONY: all hooks gate-tool-test regexp wasm-rt wasm-test rbs_extract rbs-test rbs-seed-test rbs-seed-extractor cident plan-check-test repr-check-test traits-check-test bop-arity-check-test arity-spec-check re-lit-test reject-test cli-opts-test link-names-test defer-refusals-test check-stores-test backtrace-test gc-minor-test thread-puts-test ext-test ext-cruby-test alloc-report-test rubyspec rubyspec-gate spin-check \
test test-run clean-test-results regen-rbs-expected \
regen-expected regen-expected-err bench optcarrot gate gate-full check gate-legs gate-test gate-bench gc-phases-test gc-stress-test gc-str-major-test threaded-render-test gc-locality-test test-corpus test-corpus-summary \
gate-optcarrot scale-test clean install uninstall deps tools
# `make all` includes the RBS extractor when vendor/rbs has been fetched
# (via `make deps`); without it the extractor is silently omitted. Built under
# build/ like other intermediates; rbs-seed-test copies it beside $(SPINEL),
# where main.c looks for it as a sibling at runtime.
RBS_EXTRACT_BIN = build/spinel_rbs_extract
ifneq ($(wildcard $(RBS_INC)/rbs/parser.h),)
RBS_EXTRACT_TARGET = $(RBS_EXTRACT_BIN)
else
RBS_EXTRACT_TARGET =
endif
# The single Spinel binary (compiler + cc driver). Defined here, before the
# `all` rule, because a rule's prerequisites are expanded as it is read.
# Built into bin/ alongside the companion tools (spinel-doctor, ...); bin/ sits
# beside lib/ so the binary resolves its runtime lib via ../lib, same as before.
SPINEL = bin/spinel
# Bundled carried-C spin packages (Path B). Defined here, before `all`, because
# GNU Make expands a rule's prerequisites immediately when the rule is read -- a
# definition further down would expand to empty in `all`'s prereq list. The
# build rule + rationale live further below (near the runtime archive).
# The probe compiles AND LINKS a use of the API sp_openssl.c needs, not just
# the header: a system with headers but no libssl, or one too old for
# TLS_client_method, would otherwise pass the header check and fail at the
# package's own link.
#
# KEG-ONLY INSTALLS. On a Homebrew host the headers and libraries are real
# but off the default search path, so a bare $(CC) fails this probe and the
# whole package -- sp_openssl.o, `require "openssl"`, and every
# packages/openssl/test/*.rb -- drops out of the build without saying so.
# A green `make test` there has not exercised the package at all, which is
# how an .expected file for it can be written from a run that never
# happened.
#
# So: probe bare first, and only if that fails ask for a prefix and probe
# AGAIN with -I/-L. A prefix is trusted only when it passes the same
# compile-and-link probe, so a stale or partial install cannot flip this to
# yes and then fail at the package's own link. On a host where the bare
# probe already passes, nothing below runs and the build is unchanged.
# TWO questions, because they have different answers. The link probe says
# libssl is here and usable. The syntax-only compile of the package's own
# source says THIS file can be built against it -- a version floor, a renamed
# API, a missing macro. A header set that answers the first and not the second
# (LibreSSL 3.1.5, whose TLS_client_method exists but whose evp.h has no
# EVP_CTRL_AEAD_SET_IVLEN) used to pass the probe and then stop `make` in the
# middle with a #error. Compiling the real file rather than a copy of its
# version guard is what keeps the two from drifting (#4253).
SP_OSSL_PROBE = $(shell d=$$(mktemp -d) && printf '\043include <openssl/ssl.h>\nint main(void){return TLS_client_method()!=0;}\n' > $$d/p.c 2>/dev/null && $(CC) $(1) $$d/p.c -lssl -lcrypto -o $$d/p >/dev/null 2>&1 && $(CC) $(1) -fsyntax-only -Ilib -Ipackages/openssl packages/openssl/sp_openssl.c >/dev/null 2>&1 && echo yes; rm -rf $$d)
OPENSSL_AVAILABLE := $(call SP_OSSL_PROBE,)
ifneq ($(OPENSSL_AVAILABLE),yes)
# `brew --prefix` first: it knows a non-default HOMEBREW_PREFIX, which the
# hardcoded pair below does not. The pair is the fallback for a host with
# the install but without brew on PATH (a CI image that untars it, say).
OPENSSL_PREFIX := $(firstword $(wildcard \
$(shell brew --prefix openssl@3 2>/dev/null) \
$(shell brew --prefix openssl 2>/dev/null) \
/opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3))
ifneq ($(OPENSSL_PREFIX),)
OPENSSL_CPPFLAGS := -I$(OPENSSL_PREFIX)/include
OPENSSL_LDFLAGS := -L$(OPENSSL_PREFIX)/lib
OPENSSL_AVAILABLE := $(call SP_OSSL_PROBE,$(OPENSSL_CPPFLAGS) $(OPENSSL_LDFLAGS))
ifeq ($(OPENSSL_AVAILABLE),yes)
# The compiler shells out to cc for a program's final link, and the -lssl
# that openssl.rb's ffi_lib puts on that line needs the -L too. Exported
# rather than threaded through each recipe because the link happens inside
# `bin/spinel`, one process further down. Set ONLY on a host that needed a
# prefix, so a default host's environment is untouched.
export CPATH := $(OPENSSL_PREFIX)/include$(if $(CPATH),:$(CPATH))
export LIBRARY_PATH := $(OPENSSL_PREFIX)/lib$(if $(LIBRARY_PATH),:$(LIBRARY_PATH))
# LIBRARY_PATH is the LINKER's search path; it says nothing to the loader.
# On macOS that is enough -- a Homebrew dylib's install name is absolute
# (`otool -D` on libssl.dylib prints the keg path), so a linked binary
# already knows where to find it at run time. An ELF host is the other way
# round: the -L leaves no RUNPATH behind, so a package test would link and
# then die at exec with `libssl.so.3: cannot open shared object file`. Same
# reason as the two above for being an export rather than a flag -- the run
# is a child process of the test recipe.
export LD_LIBRARY_PATH := $(OPENSSL_PREFIX)/lib$(if $(LD_LIBRARY_PATH),:$(LD_LIBRARY_PATH))
# The exports above live only as long as `make`: a `spinel` run afterwards
# (or `spin build`) linked `-lssl` with no -L and failed with `library
# 'ssl' not found` (#7191). The probed directory is recorded in the
# compiler (spinel_rev.h), which puts it beside the package's -l flags.
SPINEL_OPENSSL_LIBDIR := $(OPENSSL_PREFIX)/lib
endif
endif
endif
BUNDLED_NATIVE_OBJS = packages/json/sp_json.o packages/stringio/sp_stringio.o packages/strscan/sp_strscan.o packages/base64/sp_base64.o packages/tmpdir/sp_tmpdir.o packages/zlib/sp_zlib.o
ifeq ($(OPENSSL_AVAILABLE),yes)
BUNDLED_NATIVE_OBJS += packages/openssl/sp_openssl.o
endif
# ffi is glue over the SYSTEM libffi (and dlopen), so like openssl it is built
# only where the headers are: without its object `require "ffi"` is the
# builtin FFI DSL's, as before the package. -lffi reaches the link line
# through ffi.rb's ffi_lib. The probe compiles the real file.
LIBFFI_PREFIX := $(firstword $(wildcard $(shell brew --prefix libffi 2>/dev/null)))
LIBFFI_CPPFLAGS := $(shell pkg-config --cflags libffi 2>/dev/null)
LIBFFI_LIBDIR := $(patsubst -L%,%,$(firstword $(filter -L%,$(shell pkg-config --libs-only-L libffi 2>/dev/null))))
# No pkg-config entry: a Homebrew keg is still a known place to look.
ifeq ($(strip $(LIBFFI_CPPFLAGS)$(LIBFFI_LIBDIR)),)
ifneq ($(LIBFFI_PREFIX),)
LIBFFI_CPPFLAGS := -I$(LIBFFI_PREFIX)/include
LIBFFI_LIBDIR := $(LIBFFI_PREFIX)/lib
endif
endif
FFI_AVAILABLE := $(shell d=$$(mktemp -d) && printf 'int main(void){return 0;}\n' > $$d/p.c 2>/dev/null && $(CC) $$d/p.c $(if $(LIBFFI_LIBDIR),-L$(LIBFFI_LIBDIR)) -lffi -o $$d/p >/dev/null 2>&1 && $(CC) $(LIBFFI_CPPFLAGS) -fsyntax-only -Ilib -Ipackages/ffi packages/ffi/sp_ffi.c >/dev/null 2>&1 && echo yes; rm -rf $$d)
# A libffi outside the default search path: the -lffi that ffi.rb's ffi_lib
# puts on a program's link line, and the loader at run time, need the
# directory too -- exported for the same reasons as OPENSSL_PREFIX's above.
ifneq ($(LIBFFI_LIBDIR),)
export LIBRARY_PATH := $(LIBFFI_LIBDIR)$(if $(LIBRARY_PATH),:$(LIBRARY_PATH))
export LD_LIBRARY_PATH := $(LIBFFI_LIBDIR)$(if $(LD_LIBRARY_PATH),:$(LD_LIBRARY_PATH))
endif
# The package lays out pointers and `long` as 64-bit: not on a 32-bit target.
ifeq ($(SPINEL_INT_BITS),32)
FFI_AVAILABLE := no
endif
ifeq ($(FFI_AVAILABLE),yes)
BUNDLED_NATIVE_OBJS += packages/ffi/sp_ffi.o
endif
# Threaded variant of every bundled package object. A program that uses threads
# compiles its TU (and links the runtime archive) with -DSP_THREADS, which makes
# the runtime's per-worker globals thread-local; a package object built without
# it references them as non-TLS and the link fails (#3342). The driver picks the
# matching variant.
BUNDLED_NATIVE_MT_OBJS = $(BUNDLED_NATIVE_OBJS:.o=_mt.o)
PKG_MT_FLAGS = -DSP_THREADS -ftls-model=initial-exec
all: regexp $(SPINEL) $(RBS_EXTRACT_TARGET) tools $(BUNDLED_NATIVE_OBJS) $(BUNDLED_NATIVE_MT_OBJS)
# ---- Dependencies ----
deps: vendor/prism/include/prism/diagnostic.h vendor/rbs/include/rbs/parser.h
# Download the pre-built Prism gem from rubygems.org and extract its C
# sources (the .gem ships the generated headers -- no rake/bundler needed).
vendor/prism/include/prism/diagnostic.h:
@mkdir -p vendor/prism
@echo "Fetching prism v$(PRISM_VERSION) from rubygems.org..."
@tmpdir=$$(mktemp -d); \
curl -sL -o $$tmpdir/prism-$(PRISM_VERSION).gem https://rubygems.org/gems/prism-$(PRISM_VERSION).gem && \
tar -xf $$tmpdir/prism-$(PRISM_VERSION).gem -C $$tmpdir data.tar.gz; \
tar -xzf $$tmpdir/data.tar.gz -C vendor/prism; \
rm -rf $$tmpdir
@test -f $@ && echo "prism v$(PRISM_VERSION) ready at vendor/prism"
# Same shape: download the rbs gem and extract its bundled C parser.
vendor/rbs/include/rbs/parser.h:
@mkdir -p vendor/rbs
@echo "Fetching rbs v$(RBS_VERSION) from rubygems.org..."
@tmpdir=$$(mktemp -d); \
curl -sL -o $$tmpdir/rbs-$(RBS_VERSION).gem https://rubygems.org/gems/rbs-$(RBS_VERSION).gem && \
tar -xf $$tmpdir/rbs-$(RBS_VERSION).gem -C $$tmpdir data.tar.gz; \
tar -xzf $$tmpdir/data.tar.gz -C vendor/rbs; \
rm -rf $$tmpdir
@test -f $@ && echo "rbs v$(RBS_VERSION) ready at vendor/rbs"
# A source archive that builds with no network: the tree at HEAD as git sees
# it, plus the two vendored parsers `make deps` would otherwise fetch from
# rubygems.org (#4447). Named after the release the tree belongs to (the same
# string `spinel --version` prints), so `make dist` at a release tag is the
# release's own tarball; .github/workflows/release.yml attaches it to the
# GitHub release when a tag is pushed. The recipient runs `make` -- `deps` is
# already satisfied by the vendored sources -- and needs only a C compiler.
DIST_RELEASE = $(shell git describe --tags --match '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]' \
--match '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9].[0-9]*' 2>/dev/null \
| sed -e 's/^$$/unreleased/' -e 's/-\([0-9][0-9]*\)-g[0-9a-f]*$$/+\1/')
DIST_NAME = spinel-$(if $(DIST_RELEASE),$(DIST_RELEASE),unreleased)
.PHONY: dist
dist: deps
@mkdir -p build/dist
@rm -rf build/dist/$(DIST_NAME) build/dist/$(DIST_NAME).tar.xz
git archive --format=tar --prefix=$(DIST_NAME)/ HEAD | tar -xf - -C build/dist
@printf '%s\n%s\n' "$$(git rev-parse --short HEAD)" "$(DIST_RELEASE)" > build/dist/$(DIST_NAME)/.spinel-dist
@mkdir -p build/dist/$(DIST_NAME)/vendor
cp -R vendor/prism vendor/rbs build/dist/$(DIST_NAME)/vendor/
tar -C build/dist -cJf build/dist/$(DIST_NAME).tar.xz $(DIST_NAME)
@rm -rf build/dist/$(DIST_NAME)
@ls -l build/dist/$(DIST_NAME).tar.xz
# If PRISM_DIR ended up empty (no vendor/prism, no gem), halt with a clear
# message before trying to build anything that needs it.
ifeq ($(PRISM_DIR),)
regexp all: prism-missing
prism-missing:
@echo "Error: Prism not found."; \
echo " Run 'make deps' to fetch libprism into vendor/prism,"; \
echo " or install the prism gem (gem install prism),"; \
echo " or set PRISM_DIR=/path/to/prism manually."; \
exit 1
endif
# ---- Prism static library ----
build/libprism.a: $(PRISM_OBJ)
@mkdir -p build
ar rcs $@ $^
build/prism/%.o: $(PRISM_DIR)/src/%.c
@mkdir -p $(dir $@)
$(CC) -c $(COPT) -I$(PRISM_INC) -I$(PRISM_DIR)/src $< -o $@
# ---- rbs static library ----
build/librbs.a: $(RBS_OBJ)
@mkdir -p build
ar rcs $@ $^
build/rbs/%.o: $(RBS_DIR)/src/%.c
@mkdir -p $(dir $@)
$(CC) -c $(COPT) -Wno-all -I$(RBS_INC) -I$(RBS_DIR)/src $< -o $@
# ---- C compiler (src/) ----
# The single-binary C reimplementation of the analyzer + code generator.
# Links src/spinel_parse.c, the library copy of the Prism walk (no main();
# exposes sp_parse_file_to_text).
# `spinel` is the single binary: it emits C and then drives cc to link it.
# (SPINEL itself is defined above, just before the `all` target.)
SPINEL_HDRS = src/builtin_ops.h src/builtin_name_traits.inc src/builtin_zero_ops.inc src/builtin_arity.inc src/codegen_call_arms.h src/builtin_names.h src/ty_traits.inc src/call_plan.h src/codegen_poly.h src/repr.h src/node_table.h src/codegen.h src/codegen_internal.h src/types.h src/compiler.h src/analyze.h src/analyze_internal.h src/ffi_spec.h src/csplit.h
build/csrc/analyze_desugar.o build/csrc-work/analyze_desugar.o build/csrc/codegen_call.o build/csrc-work/codegen_call.o: $(wildcard src/*_method_names.inc)
SPINEL_OBJ = build/csrc/node_table.o build/csrc/types.o build/csrc/compiler.o \
build/csrc/ffi_spec.o \
build/csrc/analyze.o build/csrc/analyze_util.o build/csrc/analyze_infer.o build/csrc/analyze_infer_recv.o \
build/csrc/analyze_scope.o build/csrc/analyze_pass.o build/csrc/analyze_desugar.o build/csrc/repr.o build/csrc/codegen.o build/csrc/codegen_util.o build/csrc/ty_traits_check.o \
build/csrc/codegen_fold.o build/csrc/codegen_call.o build/csrc/codegen_call_poly.o build/csrc/codegen_call_method.o build/csrc/codegen_call_io.o build/csrc/codegen_call_kernel.o build/csrc/codegen_call_exception.o build/csrc/codegen_call_module.o build/csrc/codegen_call_string.o build/csrc/codegen_call_class.o build/csrc/codegen_call_operator.o build/csrc/codegen_call_object.o build/csrc/codegen_ops.o build/csrc/codegen_call_concurrency.o build/csrc/codegen_call_numeric.o build/csrc/codegen_call_hash.o build/csrc/codegen_call_array.o build/csrc/codegen_view.o build/csrc/builtin_ops.o build/csrc/builtin_names.o build/csrc/codegen_call_recv.o build/csrc/codegen_iter.o build/csrc/call_plan.o build/csrc/codegen_poly_plan.o \
build/csrc/codegen_expr.o build/csrc/codegen_stmt.o build/csrc/csplit.o build/csrc/main.o
# The decision registry (--decisions, --decisions-log; `make decisions-test`).
SPINEL_HDRS += src/decide.h
SPINEL_OBJ += build/csrc/decide.o
build/csrc:
@mkdir -p build/csrc
# -Werror=return-type: a compiler function that falls off its end returns
# garbage under -O2, and the CFLAGS this is built with may turn the warning
# off (-Wno-all); a moved rule once lost its last return this way.
build/csrc/%.o: src/%.c $(SPINEL_HDRS) | build/csrc
$(CC) $(CFLAGS) -Werror=return-type -Isrc -Ibuild/csrc -c $< -o $@
# Build revision, embedded in `spinel --version` (and spin's probe records).
# cmp-guarded so only a HEAD move recompiles main.o, not every build.
# The tmp name carries the PID: gate runs test/bench/optcarrot as parallel
# sub-makes, each re-evaluating this FORCE target -- a shared tmp name races
# (one job's rm strands the other's mv mid-flight).
# SPINEL_RELEASE is the release this build belongs to, read from the nearest
# tag: "2026.09.08" when HEAD is the release, "2026.09.08+12" when it is twelve
# commits past it, "unreleased" before the first tag. The --match patterns ARE
# the format rule -- a tag shaped any other way is not a release, and is
# ignored here rather than breaking anyone's build. The revision is git's own
# short form (unique in the repository, seven digits at least): it is what
# identifies a build (two builds of one release share a name and differ
# here), and tools/spin.rb reads it from the parentheses of `spinel --version`
# for the toolchain key its probe records are stored under.
# A source archive from `make dist` carries no .git: it records the revision
# and release it was cut from in .spinel-dist, and a build from it reads them
# there so its `spinel --version` names the build it is.
build/csrc/spinel_rev.h: FORCE | build/csrc
@r=$$(git rev-parse --short HEAD 2>/dev/null); \
d=$$(git describe --tags --match '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]' \
--match '[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9].[0-9]*' 2>/dev/null); \
if [ -z "$$r" ] && [ -f .spinel-dist ]; then r=$$(sed -n 1p .spinel-dist); d=$$(sed -n 2p .spinel-dist); fi; \
[ -n "$$r" ] || r=unknown; \
case "$$d" in \
"") d=unreleased ;; \
*-*-g*) d="$${d%-*-g*}+$$(echo "$$d" | sed 's/.*-\([0-9][0-9]*\)-g[0-9a-f]*$$/\1/')" ;; \
esac; \
t=$@.tmp.$$$$; \
{ echo "#define SPINEL_BUILD_REV \"$$r\""; \
echo "#define SPINEL_RELEASE \"$$d\""; \
echo "#define SPINEL_OPENSSL_LIBDIR \"$(SPINEL_OPENSSL_LIBDIR)\""; } > $$t; \
if cmp -s $$t $@; then rm -f $$t; else mv $$t $@; fi
build/csrc/main.o: build/csrc/spinel_rev.h
# The names a top-level Ruby method must not take: every first segment of an
# sp_* identifier the runtime owns. Hand-keeping this list is what let `def gcd`
# and `def gets` collide with sp_gcd / sp_gets -- the set is a fact about the
# runtime sources, so read it from them. Deliberately over-inclusive: an extra
# name only means one more method carries the rb_ infix, while a missing one is
# a C redeclaration in generated code. `rb` itself is in so a user `def rb_x`
# cannot land on the same symbol as an infixed `def x`. cmp-guarded like
# spinel_rev.h, so only a real change recompiles.
SP_RT_NAME_SRC = $(wildcard lib/*.h lib/*.c packages/*/*.h packages/*/*.c)
build/csrc/sp_rt_names.h: $(SP_RT_NAME_SRC) | build/csrc
@t=$@.tmp.$$$$; \
{ echo "/* generated from the runtime sources; see the Makefile rule */"; \
echo "static const char *const SP_RT_PREFIXES[] = {"; \
{ grep -hoE '\bsp_[a-z][a-z0-9_]*' $(SP_RT_NAME_SRC) 2>/dev/null \
| sed 's/^sp_//' | cut -d_ -f1; echo rb; } \
| sort -u | sed 's/.*/ "&",/'; \
echo " NULL"; echo "};"; } > $$t; \
if cmp -s $$t $@; then rm -f $$t; else mv $$t $@; fi
build/csrc/codegen_util.o: build/csrc/sp_rt_names.h
FORCE:
build/csrc/sp_parse_lib.o: src/spinel_parse.c src/sp_macro.c $(PRISM_LIB) | build/csrc
$(CC) $(CFLAGS) -I$(PRISM_INC) -c src/spinel_parse.c -o $@
# The compiler links the regexp engine so it can compile a literal at build
# time and refuse one the engine cannot read, where it used to reach the
# engine only at the built program's startup. src/re_lit_check.c is the seam
# (and carries the sp_sprintf the engine's object file references).
# the regexp engine is mruby-regexp as mruby carries it; shim/ answers its
# mruby API and re_spinel.c is spinel's side of it
RE_HDRS = $(wildcard lib/regexp/*.h lib/regexp/*.inc lib/regexp/shim/*.h lib/regexp/shim/mruby/*.h)
build/csrc/re_lit_check.o: src/re_lit_check.c $(RE_HDRS) | build/csrc
$(CC) $(CFLAGS) -Ilib/regexp -Ilib/regexp/shim -c src/re_lit_check.c -o $@
# Defined HERE, above the first rule that names it. GNU make expands a rule's
# prerequisites when the rule is READ, so with this further down the file the
# $(RE_OBJ) below expanded to nothing: `make bin/spinel` linked whatever
# regexp objects happened to be on disk and never rebuilt a stale one. The
# recipe's own $(RE_OBJ) expands at run time, so the link named the right
# files -- which is what made it look like the compiler was ignoring an edit.
RE_SRC = lib/regexp/re_compile.c lib/regexp/re_exec.c lib/regexp/re_utf8.c lib/regexp/unicase.c lib/regexp/re_spinel.c
RE_OBJ = $(patsubst lib/regexp/%.c,build/regexp/%.o,$(RE_SRC))
$(SPINEL): $(SPINEL_OBJ) build/csrc/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB)
@mkdir -p bin
$(CC) $(CFLAGS) $(SPINEL_OBJ) build/csrc/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB) -lm $(LDFLAGS) -o $@
@# Dev convenience: a repo-root `./spinel` pointing at the built binary
@# (the installed command is `spinel` too). Best-effort; gitignored.
@ln -sf $@ spinel 2>/dev/null || cp $@ spinel 2>/dev/null || true
# The compiler again with SP_WORK_COUNT: every node access counts one unit and
# the total is printed at exit (see NT_WORK in src/node_table.h). Only the
# scaling test uses it; -O1 because the count does not depend on optimization.
SPINEL_WORK = build/spinel-work
SPINEL_WORK_OBJ = $(patsubst build/csrc/%.o,build/csrc-work/%.o,$(SPINEL_OBJ))
build/csrc-work/%.o: src/%.c $(SPINEL_HDRS) | build/csrc
@mkdir -p build/csrc-work
$(CC) $(CFLAGS) -O1 -DSP_WORK_COUNT -Isrc -Ibuild/csrc -c $< -o $@
# the generated headers, as for the plain objects above: without the second,
# `make build/spinel-work` (or `make scale-test`) in a fresh tree compiled
# codegen_util.c before sp_rt_names.h existed and stopped
build/csrc-work/main.o: build/csrc/spinel_rev.h
build/csrc-work/codegen_util.o: build/csrc/sp_rt_names.h
$(SPINEL_WORK): $(SPINEL_WORK_OBJ) build/csrc/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB)
$(CC) $(CFLAGS) $(SPINEL_WORK_OBJ) build/csrc/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB) -lm $(LDFLAGS) -o $@
# The compiler again under AddressSanitizer and UndefinedBehaviorSanitizer,
# for `make san-check` (tools/san_check.sh): every program of the corpus
# compiled to C by it, and a report from either sanitizer fails. A memo that
# still points into a table a pass has since edited reads freed memory and
# the compile finishes all the same, so no test sees it; here it stops. The
# parser's side (spinel_parse.c, sp_macro.c) is instrumented too; prism and
# the regexp engine are linked as they are. Not built by default and not a
# gate leg: the build takes minutes and so does the pass.
SPINEL_SAN = build/spinel-san
SAN_FLAGS = -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined
SPINEL_SAN_OBJ = $(patsubst build/csrc/%.o,build/csrc-san/%.o,$(SPINEL_OBJ))
build/csrc-san/%.o: src/%.c $(SPINEL_HDRS) | build/csrc
@mkdir -p build/csrc-san
$(CC) $(CFLAGS) $(SAN_FLAGS) -Isrc -Ibuild/csrc -c $< -o $@
build/csrc-san/main.o: build/csrc/spinel_rev.h
build/csrc-san/codegen_util.o: build/csrc/sp_rt_names.h
build/csrc-san/sp_parse_lib.o: src/spinel_parse.c src/sp_macro.c $(PRISM_LIB) | build/csrc
@mkdir -p build/csrc-san
$(CC) $(CFLAGS) $(SAN_FLAGS) -I$(PRISM_INC) -c src/spinel_parse.c -o $@
$(SPINEL_SAN): $(SPINEL_SAN_OBJ) build/csrc-san/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB)
$(CC) $(CFLAGS) $(SAN_FLAGS) $(SPINEL_SAN_OBJ) build/csrc-san/sp_parse_lib.o build/csrc/re_lit_check.o $(RE_OBJ) $(PRISM_LIB) -lm $(LDFLAGS) -o $@
.PHONY: san-check
san-check: $(SPINEL_SAN)
@tools/san_check.sh
# Wrapper around the system `timeout` that always returns GNU coreutils'
# exit code (124 on timeout), regardless of which `timeout` is on PATH.
# The bench target keys on 124 to mark a run as SKIP; busybox uses 143
# and BSDs use 399, which would be misclassified. Built once from C.
# Parallel sub-makes can both rebuild it while another leg runs it. Link to
# a PID-specific name, then rename: a reader always gets a complete binary.
$(SPINEL_TIMEOUT): scripts/spinel-timeout.c
@mkdir -p $(@D)
t=$@.tmp.$$$$; trap 'rm -f $$t' 0; \
$(CC) $(CFLAGS) $< -o $$t && mv $$t $@
# ---- RBS extractor ----
# Reads sig/**/*.rbs, emits the seed-file format spinel_analyze consumes
# when invoked with `spinel --rbs DIR`.
ifeq ($(wildcard $(RBS_INC)/rbs/parser.h),)
rbs_extract: rbs-missing
rbs-missing:
@echo "Error: rbs C parser not found at $(RBS_INC)/rbs/parser.h."; \
echo " Run 'make deps' to fetch it from rubygems.org into vendor/rbs."; \
exit 1
else
rbs_extract: $(RBS_EXTRACT_BIN)
$(RBS_EXTRACT_BIN): tools/spinel_rbs_extract.c $(RBS_LIB)
@mkdir -p $(@D)
$(CC) $(CFLAGS) -I$(RBS_INC) tools/spinel_rbs_extract.c $(RBS_LIB) -o $@
endif
# ---- Runtime library (regexp + bigint + …) ----
# RE_CASE_FLAGS: the Unicode tables the regexp engine carries. Either of
# -DRE_NO_UNICODE_CASE and -DRE_NO_UNICODE_CTYPE is mruby's MRB_USE_ASCII_CTYPE
# build (lib/regexp/shim/mruby.h): ASCII-only /i folding, POSIX brackets and
# word boundaries, without the case, type and property tables (a category or
# an emoji property is then refused).
RE_CASE_FLAGS ?=
build/regexp/%.o: lib/regexp/%.c $(RE_HDRS)
@mkdir -p build/regexp
$(CC) -c $(COPT) $(SEC_FLAGS) $(RE_CASE_FLAGS) -Ilib/regexp -Ilib/regexp/shim $< -o $@
RT_HDRS = $(wildcard lib/*.h lib/regexp/*.h lib/regexp/*.inc lib/regexp/shim/*.h lib/regexp/shim/mruby/*.h)
# One rule for every lib/*.c object. The per-object header lists here used to be
# written by hand and had drifted: build/sp_array.o never named lib/sp_str.h,
# though sp_array.h includes it, so an edit to sp_str.h left a stale sp_array.o
# in the archive while the generated TU -- which includes the header directly --
# picked the change up. The two halves of one program then disagreed about an
# inline function and nothing said so. The threaded variant below already
# depended on the whole header set; this is the same answer for this half.
build/%.o: lib/%.c $(RT_HDRS)
@mkdir -p build
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib $< -o $@
# Bundled carried-C spin packages (Path B): compiled standalone, NOT into the
# runtime archive, and linked on demand only when the program requires them
# (native_obj markers -> src/main.c). Package C compiles against the stable
# spinel/runtime.h ABI (-Ilib) plus its own package headers. BUNDLED_NATIVE_OBJS
# is defined near the top (before `all`, whose prereqs expand at parse time).
packages/json/sp_json.o: packages/json/sp_json.c packages/json/sp_json.h \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib -Ipackages/json packages/json/sp_json.c -o $@
packages/json/sp_json_mt.o: packages/json/sp_json.c packages/json/sp_json.h \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib -Ipackages/json packages/json/sp_json.c -o $@
# openssl is glue over the SYSTEM libssl, so unlike the other package C it is
# built only when the headers are installed: OPENSSL_AVAILABLE probes for
# <openssl/ssl.h> and the object drops out of BUNDLED_NATIVE_OBJS when it is
# missing, leaving `require "openssl"` an unsatisfiable require rather than a
# link error. -lssl/-lcrypto reach the link line through openssl.rb's ffi_lib,
# which is parsed only when a program requires it.
packages/openssl/sp_openssl.o: packages/openssl/sp_openssl.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(OPENSSL_CPPFLAGS) -Ilib -Ipackages/openssl packages/openssl/sp_openssl.c -o $@
packages/openssl/sp_openssl_mt.o: packages/openssl/sp_openssl.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) $(OPENSSL_CPPFLAGS) -Ilib -Ipackages/openssl packages/openssl/sp_openssl.c -o $@
packages/ffi/sp_ffi.o: packages/ffi/sp_ffi.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(LIBFFI_CPPFLAGS) -Ilib -Ipackages/ffi packages/ffi/sp_ffi.c -o $@
packages/ffi/sp_ffi_mt.o: packages/ffi/sp_ffi.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) $(LIBFFI_CPPFLAGS) -Ilib -Ipackages/ffi packages/ffi/sp_ffi.c -o $@
# stringio is a native-bound spin package (Path B typed object): the struct,
# every method, and the header live in the package; the compiler knows it only
# through the native_* declarations in stringio.rb.
packages/stringio/sp_stringio.o: packages/stringio/sp_stringio.c packages/stringio/sp_stringio.h \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib -Ipackages/stringio packages/stringio/sp_stringio.c -o $@
packages/stringio/sp_stringio_mt.o: packages/stringio/sp_stringio.c packages/stringio/sp_stringio.h \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib -Ipackages/stringio packages/stringio/sp_stringio.c -o $@
# strscan is likewise a native-bound spin package; its regex matching links
# against the runtime archive's re_exec (a forward extern in the package C).
packages/strscan/sp_strscan.o: packages/strscan/sp_strscan.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib packages/strscan/sp_strscan.c -o $@
packages/strscan/sp_strscan_mt.o: packages/strscan/sp_strscan.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib packages/strscan/sp_strscan.c -o $@
# base64 carries its whole implementation; digest carries none (it binds the
# runtime's vendored sp_crypto symbols and has no object of its own).
packages/base64/sp_base64.o: packages/base64/sp_base64.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib packages/base64/sp_base64.c -o $@
packages/base64/sp_base64_mt.o: packages/base64/sp_base64.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib packages/base64/sp_base64.c -o $@
# tmpdir: Dir.tmpdir and Dir.mktmpdir, the system temp directory and a
# unique-directory creator. Pure C, no struct.
packages/tmpdir/sp_tmpdir.o: packages/tmpdir/sp_tmpdir.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib packages/tmpdir/sp_tmpdir.c -o $@
packages/tmpdir/sp_tmpdir_mt.o: packages/tmpdir/sp_tmpdir.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib packages/tmpdir/sp_tmpdir.c -o $@
# zlib: DEFLATE, zlib and gzip in the package's own C. No system libz and so
# no availability probe: a host with libz.so.1 and no zlib.h -- an ordinary
# box without the -dev package -- would drop the package and give a green
# `make test` that never ran it. Pure C, no struct.
packages/zlib/sp_zlib.o: packages/zlib/sp_zlib.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib packages/zlib/sp_zlib.c -o $@
packages/zlib/sp_zlib_mt.o: packages/zlib/sp_zlib.c \
lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h lib/sp_compat.h
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(PKG_MT_FLAGS) -Ilib packages/zlib/sp_zlib.c -o $@
build/sp_cold.o: lib/sp_cold.c $(RT_HDRS)
@mkdir -p build
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) -Ilib -Ilib/regexp -Ilib/regexp/shim lib/sp_cold.c -o build/sp_cold.o
SP_RT_LIB = lib/libspinel_rt.a
RT_MEMBERS = sp_bigint sp_crypto sp_pack sp_time sp_core sp_net sp_system sp_gc sp_slab sp_alloc sp_dtoa sp_marshal sp_format sp_string sp_inspect sp_array sp_str sp_str_crypt sp_hash sp_proc sp_exc sp_re sp_random sp_fiber sp_sched sp_io sp_iobuffer sp_cold sp_process sp_process_status
$(SP_RT_LIB): $(RE_OBJ) $(addprefix build/,$(addsuffix .o,$(RT_MEMBERS)))
ar rcs $@ $^
# ---- Threaded runtime variant (-DSP_THREADS) ----
# Same sources, compiled with -DSP_THREADS, linked when the program uses
# Thread/Mutex/Queue/... (see codegen's SPINEL_USES_THREADS marker and the
# archive selection in src/main.c). Phase 0 has no SP_THREADS #ifdefs yet, so it
# is functionally identical today; it is the linchpin for Phase 1 parallelism
# (per-worker __thread GC state, locks, real OS workers) without touching the
# byte-identical single-threaded path. -ftls-model=initial-exec keeps the
# eventual per-worker TLS reads a single segment-relative load.
SP_RT_MT_LIB = lib/libspinel_rt_mt.a
MT_DEF = -DSP_THREADS -ftls-model=initial-exec
# Specific rule before generic: GNU Make 3.81 (macOS system make) picks the
# first matching pattern rule, not the shortest-stem one (3.82+).
build/mt/regexp/%.o: lib/regexp/%.c $(RE_HDRS)
@mkdir -p $(@D)
$(CC) -c $(COPT) $(SEC_FLAGS) $(MT_DEF) -Ilib/regexp -Ilib/regexp/shim $< -o $@
build/mt/%.o: lib/%.c $(RT_HDRS)
@mkdir -p $(@D)
$(CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(MT_DEF) -Ilib -Ilib/regexp -Ilib/regexp/shim $< -o $@
RE_MT_OBJ = $(patsubst lib/regexp/%.c,build/mt/regexp/%.o,$(RE_SRC))
$(SP_RT_MT_LIB): $(RE_MT_OBJ) $(addprefix build/mt/,$(addsuffix .o,$(RT_MEMBERS)))
ar rcs $@ $^
# ---- ThreadSanitizer build of the threaded runtime (Phase 1 validation) ----
# The single-threaded gate links the plain archive even for threaded tests
# (test-run does its own cc), so it never exercises the mt archive's parallel
# paths. This TSan-instrumented mt archive is the race-checking gate for the
# real-parallelism work: build a threaded program against it (see scripts/
# tsan-run.sh) and run -- TSan flags any data race on the shared GC heap, the
# thread registry, or the run queue. Not built by default (TSan slows the build
# and the binary); `make tsan-archive` on demand.
SP_RT_MT_TSAN_LIB = lib/libspinel_rt_mt_tsan.a
TSAN_DEF = $(MT_DEF) -fsanitize=thread -g
# Specific before generic, as in the mt pair above.
build/mt-tsan/regexp/%.o: lib/regexp/%.c $(RE_HDRS)
@mkdir -p $(@D)
$(CC) -c -O1 $(SEC_FLAGS) $(TSAN_DEF) -Ilib/regexp -Ilib/regexp/shim $< -o $@
build/mt-tsan/%.o: lib/%.c $(RT_HDRS)
@mkdir -p $(@D)
$(CC) -c -O1 -Wno-all $(SEC_FLAGS) $(TSAN_DEF) -Ilib -Ilib/regexp -Ilib/regexp/shim $< -o $@
RE_MT_TSAN_OBJ = $(patsubst lib/regexp/%.c,build/mt-tsan/regexp/%.o,$(RE_SRC))
$(SP_RT_MT_TSAN_LIB): $(RE_MT_TSAN_OBJ) $(addprefix build/mt-tsan/,$(addsuffix .o,$(RT_MEMBERS)))
ar rcs $@ $^
tsan-archive: $(SP_RT_MT_TSAN_LIB)
# ---- wasm32-wasi runtime archive ----
# `spinel --target=wasm32-wasi` links lib/wasm32-wasi/libspinel_rt.a, the
# same sources compiled by the wasi-sdk's clang (WASI_SDK, or the sdk's own
# WASI_SDK_PATH, else /opt/wasi-sdk) with lib/wasi's stand-ins for the POSIX
# headers wasi-libc leaves out and the flags the driver passes the program
# (src/main.c: the same list, so the two halves of one module agree).
# Single-threaded only: wasm has no threads without SharedArrayBuffer. The
# bundled packages get a `<stem>_wasi.o` each, which the driver links in
# place of the native object; openssl is glue over a system libssl and has
# none. Not built by default: `make wasm-rt` on demand, and only where the
# sdk is.
WASI_SDK ?= $(if $(WASI_SDK_PATH),$(WASI_SDK_PATH),/opt/wasi-sdk)
WASI_CC = $(WASI_SDK)/bin/clang
WASI_AR = $(WASI_SDK)/bin/llvm-ar
WASI_CFLAGS = -Ilib/wasi -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_MMAN \
-mllvm -wasm-enable-sjlj -mllvm -wasm-use-legacy-eh=false
SP_RT_WASI_LIB = lib/wasm32-wasi/libspinel_rt.a
WASI_SHIM_HDRS = $(wildcard lib/wasi/*.h lib/wasi/sys/*.h)
build/wasm32-wasi/regexp/%.o: lib/regexp/%.c $(RE_HDRS)
@mkdir -p $(@D)
$(WASI_CC) -c $(COPT) $(SEC_FLAGS) $(RE_CASE_FLAGS) $(WASI_CFLAGS) -Ilib/regexp -Ilib/regexp/shim $< -o $@
build/wasm32-wasi/wasi/%.o: lib/wasi/%.c $(WASI_SHIM_HDRS)
@mkdir -p $(@D)
$(WASI_CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(WASI_CFLAGS) -Ilib $< -o $@
build/wasm32-wasi/%.o: lib/%.c $(RT_HDRS) $(WASI_SHIM_HDRS)
@mkdir -p $(@D)
$(WASI_CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(WASI_CFLAGS) -Ilib -Ilib/regexp -Ilib/regexp/shim $< -o $@
RE_WASI_OBJ = $(patsubst lib/regexp/%.c,build/wasm32-wasi/regexp/%.o,$(RE_SRC))
WASI_SHIM_OBJ = $(patsubst lib/wasi/%.c,build/wasm32-wasi/wasi/%.o,$(wildcard lib/wasi/*.c))
$(SP_RT_WASI_LIB): $(RE_WASI_OBJ) $(WASI_SHIM_OBJ) $(addprefix build/wasm32-wasi/,$(addsuffix .o,$(RT_MEMBERS)))
@mkdir -p $(@D)
rm -f $@ && $(WASI_AR) rcs $@ $^
BUNDLED_NATIVE_WASI_OBJS = $(patsubst %.o,%_wasi.o,$(filter-out packages/openssl/% packages/ffi/%,$(BUNDLED_NATIVE_OBJS)))
packages/%_wasi.o: packages/%.c lib/spinel/runtime.h lib/sp_alloc.h lib/sp_gc.h lib/sp_types.h $(WASI_SHIM_HDRS)
$(WASI_CC) -c $(COPT) -Wno-all $(SEC_FLAGS) $(WASI_CFLAGS) -Ilib -I$(@D) $< -o $@
wasm-rt: $(SP_RT_WASI_LIB) $(BUNDLED_NATIVE_WASI_OBJS)
# A few of the corpus's programs built with --target=wasm32-wasi and run
# under wasmtime (WASMTIME, default: the one on PATH) against their expected
# output: exceptions (the sjlj lowering), regexps, floats, a Bignum, a
# bundled package, a 32-bit Integer. Not in the gate: it needs the sdk and
# an engine. `make wasm-test` where both are.
WASMTIME ?= wasmtime
WASM_TESTS = test/string_gsub_block.rb test/rescue_roots_under_collection.rb test/float_round_half.rb \
test/bignum_modulo_bit_pow.rb test/json_user_to_json_bytes.rb test/array_flatten_typed_elements.rb \
test/string_to_i_overflow_raises.rb
# WASM_ENGINE_REQUIRED=1 makes a missing engine a failure rather than a skip:
# the one job whose whole purpose is to prove these programs still link and run
# cannot report that by exiting 0 with "skipped" (#4807). CI sets it.
wasm-test: $(SPINEL) wasm-rt
@if ! command -v $(WASMTIME) >/dev/null 2>&1; then \
if [ -n "$(WASM_ENGINE_REQUIRED)" ]; then echo "wasm-test: FAIL (no $(WASMTIME), and WASM_ENGINE_REQUIRED is set)"; exit 1; fi; \
echo "wasm-test: skipped (no $(WASMTIME))"; exit 0; fi; \
tmp=$$(mktemp -d /tmp/spinel-wasm.XXXXXX); ok=1; \
for t in $(WASM_TESTS); do \
bn=$$(basename $$t .rb); \
if ! WASI_SDK=$(WASI_SDK) $(SPINEL) --target=wasm32-wasi --no-line-map $$t -o $$tmp/$$bn.wasm >/dev/null 2>$$tmp/$$bn.build; then \
echo "wasm-test: FAIL (build) $$t"; head -3 $$tmp/$$bn.build; ok=0; continue; fi; \
$(WASMTIME) run -W max-wasm-stack=16777216 --dir=. $$tmp/$$bn.wasm >$$tmp/$$bn.out 2>$$tmp/$$bn.err </dev/null; \
if cmp -s $$tmp/$$bn.out $$t.expected; then echo "wasm-test: pass $$t"; \
else echo "wasm-test: FAIL $$t"; diff -u $$t.expected $$tmp/$$bn.out | head -6; head -3 $$tmp/$$bn.err; ok=0; fi; \
done; \
rm -rf $$tmp; [ $$ok -eq 1 ]
regexp: $(SP_RT_LIB) $(SP_RT_MT_LIB)
# ---- In-tree developer tools ----
# spinel-doctor / spinel-reduce / spinel-flatten: written in the spinel subset
# and compiled by spinel itself (dogfood), so their only runtime dependency is
# cc -- the same as the compiler. Each tools/<name>.rb becomes bin/spinel-<name>,
# beside the compiler, so the `spinel-<name>` command is found next to `spinel`.
# A tool that no longer fits the subset breaks the build, which keeps them honest.
TOOL_NAMES = doctor reduce flatten diff bisect
TOOL_BINS = $(addprefix bin/spinel-,$(TOOL_NAMES))
tools: $(TOOL_BINS) bin/spin
# spin: the project tool (self-hosted; see docs/spin.md). Stages the RBS
# extractor beside the compiler when vendor/rbs is fetched: a spin-driven
# build resolves --rbs via <dir-of-spinel>/spinel_rbs_extract, and without
# the copy it silently lost every .rbs seed (#1845 bounce 6).
# spin's own identity for `spin --version`: the same release and revision
# stamp the compiler carries (build/csrc/spinel_rev.h), rendered as a Ruby
# file spin requires, so the two say the same thing when they ship together.
# cmp-guarded like the header so only a HEAD move rebuilds spin.
build/spin_version.rb: build/csrc/spinel_rev.h
@t=$@.tmp.$$$$; \
{ echo "SPIN_RELEASE = \"$$(sed -n 's/^#define SPINEL_RELEASE "\(.*\)"/\1/p' build/csrc/spinel_rev.h)\""; \
echo "SPIN_BUILD_REV = \"$$(sed -n 's/^#define SPINEL_BUILD_REV "\(.*\)"/\1/p' build/csrc/spinel_rev.h)\""; } > $$t; \
if cmp -s $$t $@; then rm -f $$t; else mv $$t $@; fi
bin/spin: tools/spin.rb tools/spin/toml.rb build/spin_version.rb $(SPINEL) $(SP_RT_LIB) $(SP_RT_MT_LIB) $(RBS_EXTRACT_TARGET)
$(SPINEL) -I build tools/spin.rb -o bin/spin
@if [ -n "$(RBS_EXTRACT_TARGET)" ]; then \
cp -f $(RBS_EXTRACT_BIN) bin/spinel_rbs_extract; \
echo "$(RBS_EXTRACT_BIN) -> bin/spinel_rbs_extract"; \
fi
bin/spinel-%: tools/%.rb tools/tool_common.rb $(SPINEL) $(SP_RT_LIB) $(SP_RT_MT_LIB)
@mkdir -p bin
$(SPINEL) $< -o $@
bin/spinel-bisect: tools/bisect_search.rb
# ---- Test ----
TESTS := $(wildcard test/*.rb)
# Build-incompatible: regexp_unicode_casefold and regexp_unicode_ctype pin
# what the Unicode case and type tables answer, and either RE_NO_UNICODE_*
# switch is the ASCII build that leaves both out.
ifneq (,$(findstring RE_NO_UNICODE_,$(RE_CASE_FLAGS)))
TESTS := $(filter-out test/regexp_unicode_casefold.rb test/regexp_unicode_ctype.rb,$(TESTS))
endif
# Mode-incompatible: int_overflow_raises pins raise-mode semantics; under
# --int-overflow=promote the same code auto-promotes and output diverges.
# float_to_int_out_of_range and float_to_int_boundary are the same case for
# the Float -> Integer conversions: they pin the RangeError raise mode keeps,
# which promote answers as a Bignum instead (#4688). The promote answers are
# pinned by promote_float_to_int.rb. str_to_i_overflow,
# string_to_i_overflow_raises and integer_argument_error's LLONG_MIN line pin
# the RangeError String#to_i and Integer() answer past sp_int in raise mode;
# promote reads a Bignum there, pinned by promote_str_to_i_bigint.rb.
# poly_call_legacy_abi_gate / poly_call_fast_abi_gate pin the raise/wrap legacy
# sp_int Method ABI classification; under promote the poly-ABI stamp gates the
# same dynamic calls instead, and the two classifications legitimately diverge
# in both directions: a target promote leaves mixed-signatured (a String or
# pointer PARAMETER beside boxed ones) declines there where legacy accepts it,
# while a pointer argument into an untyped parameter is callable there (both
# slots boxed) where legacy declines. The poly-slot promote behavior is pinned
# by promote_poly_slot_method_call instead. poly_method_return_kinds
# additionally trips a typed `.to_proc`-with-defaults promote gap (an IntArray
# default in a poly-widened callee), unrelated to the dispatch these pin.
ifeq ($(SPINEL_INT_OVERFLOW),promote)
TESTS := $(filter-out test/int_overflow_raises.rb test/int_overflow_op_assign.rb test/poly_int_overflow_raises.rb test/str_to_i_overflow.rb test/string_to_i_overflow_raises.rb test/integer_argument_error.rb test/bounded_counter_unchecked_add.rb test/float_to_int_out_of_range.rb test/bigrational_to_i_out_of_range.rb test/float_to_int_boundary.rb test/poly_call_legacy_abi_gate.rb test/poly_call_fast_abi_gate.rb test/poly_method_return_kinds.rb,$(TESTS))
# Drive the spinel front-end and the C compile in promote mode so the test
# rule actually exercises the auto-promotion path end to end.
SP_OV_FLAG := --int-overflow=promote
SP_OV_DEFINE := -DSP_INT_OVERFLOW_MODE_PROMOTE
else
# `promote_*` tests overflow on purpose and only have defined output under
# --int-overflow=promote; in raise/wrap mode they would (correctly) raise.
TESTS := $(filter-out test/promote_%.rb,$(TESTS))
endif
# A 32-bit target has a 32-bit Integer (lib/sp_types.h): a test that assumes
# the 64-bit one (values or arithmetic past 2^31, `Integer#size == 8`, a
# printed hash value, a 64-bit FFI width) says so in its first line and is
# not run there. `make CC='cc -m32'` on a 64-bit host is such a target.
ifeq ($(SPINEL_INT_BITS),32)
TESTS := $(filter-out $(shell grep -l '^\# spinel: int64' test/*.rb),$(TESTS))
endif
# TEST_SHARD=k/n runs the k-th of n slices of the corpus (1-based), the
# slice taken by position in the sorted list so every test lands in exactly
# one: CI runs the slices as parallel jobs, since the corpus is what the
# jobs' wall time is made of. The bundled packages' tests are sliced the
# same way below. Unset, the whole corpus runs.
#
# The pick is make's own: the shell is asked only for the positions (k, k+n,
# k+2n, ...), and $(word) takes those. The list itself used to go through the
# shell, `printf '%s\n' <every test> | awk ...`, as one `sh -c` string. Past
# 128 KiB, Linux refuses a single argument that long (MAX_ARG_STRLEN), so
# once the corpus names outgrew it (late September 2026, about 4,200 tests)
# make printed "/bin/sh: Argument list too long" and the pick answered
# nothing. Every CI lane then ran only the packages' tests and still passed.
# macOS has no such per-argument limit, so a local run never showed it.
ifneq ($(TEST_SHARD),)
SHARD_K := $(word 1,$(subst /, ,$(TEST_SHARD)))
SHARD_N := $(word 2,$(subst /, ,$(TEST_SHARD)))
shard_pick = $(foreach i,$(shell seq $(SHARD_K) $(SHARD_N) $(words $(1))),$(word $(i),$(1)))
SHARD_ALL := $(TESTS)
TESTS := $(call shard_pick,$(TESTS))
# A slice that comes out empty from a non-empty corpus means the pick
# failed. Stop, rather than let a green run test nothing.
ifneq ($(SHARD_ALL),)
ifeq ($(TESTS),)
$(error TEST_SHARD=$(TEST_SHARD) picked no test out of $(words $(SHARD_ALL)))
endif
endif
endif
TEST_TARGETS := $(patsubst test/%.rb,build/test-results/%.ok,$(TESTS))
# Bundled spin packages carry their own test/*.rb (the same snapshot contract,
# runnable with `spin test` inside the package). The compiler gate runs them
# too -- bundled packages are versioned with the compiler, so a compiler change
# that breaks one must fail here, not at package-publish time. Targets are
# namespaced pkg.<package>.<test>.ok to avoid colliding with test/ names.
PKG_TESTS := $(wildcard packages/*/test/*.rb)
# The openssl package is glue over the SYSTEM libssl, so its tests only exist
# where the headers do -- the object drops out of BUNDLED_NATIVE_OBJS on the
# same probe, and `require "openssl"` is then an unsatisfiable require.
ifneq ($(OPENSSL_AVAILABLE),yes)
PKG_TESTS := $(filter-out packages/openssl/test/%.rb,$(PKG_TESTS))
endif
ifneq ($(FFI_AVAILABLE),yes)
PKG_TESTS := $(filter-out packages/ffi/test/%.rb packages/fiddle/test/%.rb,$(PKG_TESTS))
endif
ifeq ($(SPINEL_INT_BITS),32) # the same first-line marker as test/*.rb
PKG_TESTS := $(filter-out $(shell grep -l '^\# spinel: int64' packages/*/test/*.rb),$(PKG_TESTS))
endif
ifneq ($(TEST_SHARD),)
PKG_TESTS := $(call shard_pick,$(PKG_TESTS))
endif
pkg_of = $(word 2,$(subst /, ,$(1)))
PKG_TEST_TARGETS := $(foreach t,$(PKG_TESTS),build/test-results/pkg.$(call pkg_of,$(t)).$(notdir $(t:.rb=)).ok)
# Warnings the generated-C -Werror check should not gate on. clang enables
# -Wunused-value by default (gcc only under -Wall, which the build disables),
# so a discarded value-producing statement-expression -- e.g. the
# `({ ...; v; })` emitted for `Fiber[:k] = v` in statement position -- fails
# CI under clang while gcc is silent. The value is intentionally discarded;
# behaviour is still gated by the output diff. Keep this list minimal.
TEST_WARN_SUPPRESS := -Wno-unused-value
# The main suite compiles every generated TU with -Werror, so a pointer-type
# mismatch in emitted C fails a test. The --rbs fixtures did not: they build
# with a plain cc, which is why the one family that needs an RBS signature to
# arise -- a subclass instance in a slot the signature pins to an ancestor --
# could reach a release without any host reporting it (#3418). Only this
# diagnostic is promoted, not -Werror wholesale: these fixtures deliberately
# exercise shapes that warn for other, expected reasons.
RBS_SEED_STRICT := -Werror=incompatible-pointer-types
# ---- Precompiled runtime header for the per-test compiles ----
# Every generated test TU includes the same lib/spinel_rt.h; the cc step is
# >99% of a test's cost and roughly half of that is parsing the header, so
# the suite precompiles it once per make invocation (measured: gcc -15%,
# clang -23% on the per-test compile). Two variants cover the two TU shapes
# the emitter produces: plain, and `#define SP_TU_NO_POLY_RENDER 1` before
# the include. gcc picks the .gch up implicitly from the include path; clang
# ignores gcc-style implicit lookup and needs an explicit -include-pch.
# Each variant dir also carries a copy of spinel_rt.h so gcc degrades to a
# normal textual include if the .gch is unusable. The PCH path is keyed on
# compiler kind, $(OPT) and the overflow mode because a PCH only loads under
# the exact flags it was built with. The mode was missing from the key, and
# a -D mismatch is one clang accepts SILENTLY: after a raise-mode run, the
# promote suite reused the raise-mode .gch and every test ran against
# raise-semantics runtime inlines -- the mode-sensitive tests failed and the
# rest tested the wrong runtime without a word (#4538).
CC_KIND := $(if $(findstring clang,$(shell $(CC) --version 2>/dev/null | head -1)),clang,gcc)
# The key has to be ONE path component: $(OPT) is a flag LIST, so a
# multi-flag setting (`COPT := -O2 -g0` in config.mk) put a space in the
# middle and every use of PCH_ROOT then split into two words -- two make
# targets, an -I pointing at the wrong directory, and a mkdir of /plain
# (#4256). Strip the characters that cannot appear in one component:
# the dashes the key never wanted, then spaces, slashes and equals.
sp_empty :=
sp_space := $(sp_empty) $(sp_empty)
sp_pathify = $(subst =,,$(subst /,,$(subst $(sp_space),,$(subst -,,$(1)))))
PCH_ROOT := build/pch/$(CC_KIND)$(call sp_pathify,$(OPT))$(if $(SP_OV_DEFINE),promote)
PCH_FLAGS = $(CFLAGS) $(SP_OV_DEFINE) -Werror $(TEST_WARN_SUPPRESS) $(SEC_FLAGS)
PCH_PLAIN := $(PCH_ROOT)/plain/spinel_rt.h.gch
PCH_NOPOLY := $(PCH_ROOT)/nopoly/spinel_rt.h.gch
SP_LIB_HDRS := $(wildcard lib/*.h)
$(PCH_PLAIN): $(SP_LIB_HDRS)
@mkdir -p $(@D)
@cp lib/spinel_rt.h $(@D)/spinel_rt.h
@$(CC) $(PCH_FLAGS) -Ilib -x c-header $(@D)/spinel_rt.h -o $@
$(PCH_NOPOLY): $(SP_LIB_HDRS)
@mkdir -p $(@D)
@cp lib/spinel_rt.h $(@D)/spinel_rt.h
@$(CC) $(PCH_FLAGS) -DSP_TU_NO_POLY_RENDER=1 -Ilib -x c-header $(@D)/spinel_rt.h -o $@
ifeq ($(CC_KIND),clang)
PCH_USE_PLAIN = -include-pch $(PCH_PLAIN)
PCH_USE_NOPOLY = -include-pch $(PCH_NOPOLY)
# clang tests compile+link in ONE driver invocation: sccache declines to
# cache -include-pch compiles, so the split buys nothing there, and the
# extra ~2000 driver spawns are expensive on macOS (process launch cost).
# gcc keeps the split: its separate compile step is sccache-cacheable.
TEST_SINGLE_INVOKE := 1
else
PCH_USE_PLAIN = -I$(PCH_ROOT)/plain
PCH_USE_NOPOLY = -I$(PCH_ROOT)/nopoly
TEST_SINGLE_INVOKE :=
endif
# Host CPU count (Linux nproc, macOS sysctl), a safe fallback of 4.
NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
# Default the standalone suite to parallel: inject -j<nproc> ONLY when the
# invocation supplied no job count of its own. Under `make -j gate` the leg
# already inherits the jobserver (a -j is in MAKEFLAGS), so this stays empty and
# there is no oversubscription; a bare `make test` / `make check` now fans the
# per-test .ok builds across the cores (~5x). An explicit `make -jN test` wins.
TEST_JOBS := $(if $(filter -j%,$(MAKEFLAGS)),,-j$(NPROC))
# `bench` is one monolithic recipe (a shell loop), so it can't fan out through
# the .ok pattern the way `test` does. Parallelize its loop with `xargs -P`
# instead -- but only when there is no jobserver to respect: under `make -j gate`
# the leg runs alongside test/optcarrot on the shared jobserver, so keep it
# serial (-P 1) there and let the jobserver schedule; a bare `make bench` uses
# all cores.
BENCH_PJOBS := $(if $(filter -j%,$(MAKEFLAGS)),1,$(NPROC))
# A regexp literal is two constants, the pattern and its flags, so whether the
# engine can read it is settled at compile time. It used to reach the engine
# only at the built program's startup, so a pattern like /[z-a]/ built clean
# and raised RegexpError when the program ran; CRuby reports it from the parse.
# There is no place for this in test/*.rb, whose harness needs the compile to
# succeed, so the refusal is checked here.
re-lit-test: $(SPINEL)
@tmp=$$(mktemp -d /tmp/spinel-relit.XXXXXX); ok=1; \
printf 'p(/[z-a]/i)\n' > "$$tmp/bad.rb"; \
if $(SPINEL) "$$tmp/bad.rb" -o "$$tmp/bad" >"$$tmp/bad.out" 2>&1; then \
echo "re-lit-test: FAIL (a literal the engine cannot read still built)"; ok=0; \
else \
grep -q 'bad.rb:1: empty range in char class: /\[z-a\]/i' "$$tmp/bad.out" || \
{ echo "re-lit-test: FAIL (the refusal did not name the line, the pattern and its flags)"; cat "$$tmp/bad.out"; ok=0; }; \
fi; \
printf 'x = 1\ny = 2\np(/(?<1>a)/)\n' > "$$tmp/name.rb"; \
if $(SPINEL) "$$tmp/name.rb" -o "$$tmp/name" >"$$tmp/name.out" 2>&1; then \
echo "re-lit-test: FAIL (an invalid group name still built)"; ok=0; \
else \
grep -q 'name.rb:3: invalid group name <1>' "$$tmp/name.out" || \
{ echo "re-lit-test: FAIL (the refusal named the wrong line)"; cat "$$tmp/name.out"; ok=0; }; \
fi; \
printf 'p("m" =~ /[a-z]/)\np("M" =~ /[a-z]/i)\n' > "$$tmp/good.rb"; \
if $(SPINEL) "$$tmp/good.rb" -o "$$tmp/good" >"$$tmp/good.out" 2>&1; then \
[ "$$("$$tmp/good")" = "$$(printf '0\n0')" ] || { echo "re-lit-test: FAIL (a valid literal changed behaviour)"; "$$tmp/good"; ok=0; }; \
else echo "re-lit-test: FAIL (a valid literal was refused)"; cat "$$tmp/good.out"; ok=0; fi; \
printf 'x = "z-a"\nre = /[#{x}]/\np((("m" =~ re) ? 1 : 0))\n' > "$$tmp/interp.rb"; \
if $(SPINEL) "$$tmp/interp.rb" -o "$$tmp/interp" >"$$tmp/interp.out" 2>&1; then \
"$$tmp/interp" >"$$tmp/interp.run" 2>&1 || true; \
grep -q 'empty range in char class' "$$tmp/interp.run" || \
{ echo "re-lit-test: FAIL (an interpolated pattern should stay a runtime question)"; cat "$$tmp/interp.run"; ok=0; }; \
else echo "re-lit-test: FAIL (an interpolated pattern was refused at compile time)"; cat "$$tmp/interp.out"; ok=0; fi; \
rm -rf "$$tmp"; \
if [ $$ok = 1 ]; then echo "re-lit-test: pass"; else exit 1; fi
# `make test` always runs fresh: it wipes the prior `.ok` stamps first,
# then runs the suite. (The old incremental `test` + `retest` split is
# gone -- a stale `.ok` reading PASS was a recurring foot-gun.)
test: $(SPINEL_TIMEOUT)
@if [ -z "$(TIMEOUT_BIN)" ]; then \
echo "WARNING: no 'timeout'/'gtimeout' on PATH -- tests run with NO time limit."; \
echo " A hanging test will hang this run until the CI job's own limit."; \
fi
+@$(MAKE) --no-print-directory clean-test-results
+@$(MAKE) $(TEST_JOBS) --no-print-directory test-run
# The actual run. rbs-test golden-checks the RBS extractor (cheap, C-only).
# rbs-seed-test checks the seeds actually reach the analyzer (incl. nested
# classes, #1417).
test-run: rbs-test rbs-seed-test re-lit-test reject-test cli-opts-test link-names-test defer-refusals-test check-stores-test backtrace-test gc-minor-test gc-phases-test gc-stress-test gc-threshold-test gc-obj-budget-test gc-str-major-test threaded-render-test gc-locality-test byref-capture-test thread-puts-test ext-test ext-cruby-test test-corpus-summary
# The test/*.rb corpus (and the bundled packages') on its own, without the
# C-side legs: what a 32-bit target runs (`make test-corpus CC='cc -m32'`),
# whose CRuby extension leg and rbs tooling have no 32-bit toolchain to
# build against.
test-corpus: $(SPINEL_TIMEOUT)
+@$(MAKE) --no-print-directory clean-test-results
+@$(MAKE) $(TEST_JOBS) --no-print-directory test-corpus-summary
test-corpus-summary: $(TEST_TARGETS) $(PKG_TEST_TARGETS)
@if [ -z "$(TIMEOUT_BIN)" ]; then echo "Note: no 'timeout' command found; running without time limits."; fi
@if [ -t 1 ]; then printf '\n'; fi
@pass=$$(grep -l '^PASS' build/test-results/*.ok 2>/dev/null | wc -l); \
fail=$$(grep -l '^FAIL' build/test-results/*.ok 2>/dev/null | wc -l); \
err=$$(grep -l '^ERR' build/test-results/*.ok 2>/dev/null | wc -l); \
for f in build/test-results/*.ok; do \
bn=$$(basename "$$f" .ok); \
status=$$(cat "$$f"); \
if [ "$$status" = FAIL ]; then \
echo "FAIL: $$bn"; \
head -40 "$$f.diff"; \
elif [ "$$status" = ERR ]; then \
echo "ERR: $$bn"; \
fi; \