-
Notifications
You must be signed in to change notification settings - Fork 12
/
Makefile
393 lines (332 loc) · 15.6 KB
/
Makefile
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
# By default, use a `debug` build of the Savi compiler,
# but this can be overridden by the caller to use a `release` build.
config?=debug
# Allow overriding the build dir (for example in Docker-based invocations).
BUILD?=build
MAKE_VAR_CACHE?=.make-var-cache
# Some convenience variables that set up the paths for the built Savi binaries.
SAVI=$(BUILD)/savi-$(config)
SPEC=$(BUILD)/savi-spec
CLANGXX?=clang++
CLANG?=clang
# Run the full CI suite.
ci: PHONY
${MAKE} format.check
${MAKE} gen.capnp.check
${MAKE} spec.core.deps extra_args="--backtrace $(extra_args)"
${MAKE} self-hosted.deps extra_args="--backtrace $(extra_args)"
${MAKE} spec.all extra_args="--backtrace $(extra_args)"
${MAKE} example.deps dir="examples/adventofcode/2018" extra_args="--backtrace $(extra_args)"
${MAKE} example dir="examples/adventofcode/2018" extra_args="--backtrace $(extra_args)"
${MAKE} example-eval
# Remove temporary/generated/downloaded files.
clean: PHONY
rm -rf $(BUILD) $(MAKE_VAR_CACHE) lib/libsavi_runtime
# Run the full test suite.
spec.all: PHONY spec.self-hosted.all spec.compiler.all spec.language spec.core spec.unit.all spec.integration.all
# Run the specs that are written in markdown (mostly compiler pass tests).
# Run the given compiler-spec target (or all targets).
spec.compiler: PHONY SAVI
echo && $(SAVI) compilerspec "spec/compiler/$(name).savi.spec.md" $(extra_args)
spec.compiler.all: PHONY $(SAVI)
find "spec/compiler" -name '*.savi.spec.md' | sort | xargs -I'{}' sh -c \
'echo && $(SAVI) compilerspec {} $(extra_args) || exit 255'
# Run the specs for the basic language semantics.
spec.language: PHONY SAVI
echo && $(SAVI) run --cd spec/language $(extra_args)
# Run the specs for the core package.
spec.core: PHONY SAVI
echo && $(SAVI) run --cd spec/core $(extra_args)
# Update deps for the specs for the core package.
spec.core.deps: PHONY SAVI
echo && $(SAVI) deps update --cd spec/core $(extra_args)
# Run the specs for the core package in lldb for debugging.
spec.core.lldb: PHONY SAVI
echo && $(SAVI) build --cd spec/core $(extra_args) && \
lldb -o run -- spec/core/bin/spec
# Run the specs that are written in Crystal (mostly compiler unit tests),
# narrowing to those with the given name (or all of them).
spec.unit: PHONY SPEC
echo && $(SPEC) -v -e "$(name)"
spec.unit.all: PHONY SPEC
echo && $(SPEC)
# Run the integration tests, which invoke the compiler in a real directory.
spec.integration: PHONY SAVI
echo && spec/integration/run-one.sh "$(name)" $(SAVI)
spec.integration.all: PHONY SAVI
echo && spec/integration/run-all.sh $(SAVI)
# Check formatting of *.savi source files.
format.check: PHONY SAVI
echo && $(SAVI) format --check --backtrace
# Fix formatting of *.savi source files.
format: PHONY SAVI
echo && $(SAVI) format --backtrace
# Generate FFI code.
ffigen: PHONY SAVI
echo && $(SAVI) ffigen "$(header)" $(extra_args) --backtrace
# Evaluate a Hello World example.
example-eval: PHONY SAVI
echo && $(SAVI) eval 'env.out.print("Hello, World!")' --backtrace
# Compile and run the user program binary in the given directory.
example: PHONY SAVI
echo && $(SAVI) run --cd "$(dir)" $(extra_args)
# Compile the files in the given directory.
example.compile: PHONY SAVI
echo && $(SAVI) --cd "$(dir)" $(extra_args)
# Update deps for the specs for the given example directory.
example.deps: PHONY SAVI
echo && $(SAVI) deps update --cd "$(dir)" $(extra_args)
# Generate Savi and Crystal source code from CapnProto definitions.
gen.capnp: PHONY self-hosted.deps $(BUILD)/capnpc-savi $(BUILD)/capnpc-crystal
capnp compile \
-I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \
self-hosted/src/SaviProto/SaviProto.AST.capnp --output=- \
| $(BUILD)/capnpc-savi > self-hosted/src/SaviProto/SaviProto.AST.capnp.savi
capnp compile \
-I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \
self-hosted/src/SaviProto/SaviProto.Source.capnp --output=- \
| $(BUILD)/capnpc-savi > self-hosted/src/SaviProto/SaviProto.Source.capnp.savi
capnp compile \
-I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \
self-hosted/src/SaviProto/SaviProto.AST.capnp --output=- \
| $(BUILD)/capnpc-crystal > self-hosted/src/SaviProto/SaviProto.AST.capnp.cr
capnp compile \
-I"$(shell find self-hosted/deps/github:jemc-savi/CapnProto/* -name src | sort -r -V | head -n 1)/" \
self-hosted/src/SaviProto/SaviProto.Source.capnp --output=- \
| $(BUILD)/capnpc-crystal > self-hosted/src/SaviProto/SaviProto.Source.capnp.cr
gen.capnp.check: gen.capnp
git diff --exit-code self-hosted/src/SaviProto
# Update deps for the Self-hosted Savi subprograms.
self-hosted.deps: PHONY SAVI
echo && $(SAVI) deps update --cd self-hosted --for savi-lang-parse
# Create the self-hosted Savi parse subprogram.
self-hosted/bin/savi-lang-parse: $(SAVI) $(shell find self-hosted/src/savi-lang-parse self-hosted/src/SaviProto -name '*.savi')
echo && $(SAVI) --cd self-hosted savi-lang-parse --print-perf --backtrace
# Run spec scripts for self-hosted Savi subprograms.
spec.self-hosted: PHONY self-hosted/bin/$(name)
self-hosted/spec/$(name).sh
spec.self-hosted.all: PHONY
make spec.self-hosted name=savi-lang-parse
# Compile the vscode extension.
vscode: PHONY SAVI
cd tooling/vscode && npm run-script compile || npm install
##
# General utilities
.PHONY: PHONY
SAVI: $(SAVI) lib/libsavi_runtime
SPEC: $(SPEC) lib/libsavi_runtime
# This is a bit of Makefile voodoo we use to allow us to use the value
# of a variable to invalidate a target file when it changes.
# This lets us force make to rebuild things when that variable changes.
# See https://stackoverflow.com/a/26147844
define MAKE_VAR_CACHE_FOR
$(MAKE_VAR_CACHE)/$1: PHONY
@mkdir -p $(MAKE_VAR_CACHE)
@if [ '$(shell cat $(MAKE_VAR_CACHE)/$1 2> /dev/null)' = '$($1)' ]; then echo; else \
/usr/bin/env echo -n $($1) > $(MAKE_VAR_CACHE)/$1; fi
endef
##
# Build-related targets
# We expect a CI release build to specify the version externally,
# so here we just default to unknown if it was not specified.
SAVI_VERSION?=unknown
# Use an external shell script to detect the platform.
# Currently the platform representation takes the form of an LLVM "triple".
LLVM_STATIC_PLATFORM?=$(shell ./platform.sh llvm-static)
TARGET_PLATFORM?=$(shell ./platform.sh host)
CLANG_TARGET_PLATFORM?=$(TARGET_PLATFORM)
# Specify where to download our pre-built LLVM/clang static libraries from.
# This needs to get bumped explicitly here when we do a new LLVM build.
LLVM_STATIC_RELEASE_URL?=https://github.com/savi-lang/llvm-static/releases/download/v15.0.6-20230104
$(eval $(call MAKE_VAR_CACHE_FOR,LLVM_STATIC_RELEASE_URL))
# Specify where to download our pre-built runtime bitcode from.
# This needs to get bumped explicitly here when we do a new runtime build.
RUNTIME_BITCODE_RELEASE_URL?=https://github.com/savi-lang/runtime-bitcode/releases/download/v0.20220929.0
$(eval $(call MAKE_VAR_CACHE_FOR,RUNTIME_BITCODE_RELEASE_URL))
# Specify where to download the CapnProto compiler plugin for Savi code gen.
# This needs to get bumped explicitly here when we do a new CapnProto release.
CAPNPC_SAVI_DOWNLOAD_URL?=https://github.com/jemc-savi/CapnProto/releases/download/v0.20240922.0/capnpc-savi-v0.20240922.0-$(TARGET_PLATFORM).tar.gz
$(eval $(call MAKE_VAR_CACHE_FOR,CAPNPC_SAVI_DOWNLOAD_URL))
# This is the path where we look for the LLVM pre-built static libraries to be,
# including the llvm-config utility used to print information about them.
# By default this is set up
LLVM_PATH?=$(BUILD)/llvm-static
LLVM_CONFIG?=$(LLVM_PATH)/bin/llvm-config
# Determine which flavor of the C++ standard library to link against.
# We choose libstdc++ on Linux and DragonFly, and libc++ on FreeBSD and MacOS.
ifneq (,$(findstring linux,$(TARGET_PLATFORM)))
LIB_CXX_KIND?=stdc++
else ifneq (,$(findstring dragonfly,$(TARGET_PLATFORM)))
LIB_CXX_KIND?=stdc++
else
LIB_CXX_KIND?=c++
endif
# Find the libraries we need to link against.
# We look first for a static library path, or fallback to specifying it as -l
# which will cause the linker to locate it as a dynamic library.
LIB_GC?=$(shell find /usr /opt -name libgc.a 2> /dev/null | head -n 1 | grep . || echo -lgc)
LIB_EVENT?=$(shell find /usr /opt -name libevent.a 2> /dev/null | head -n 1 | grep . || echo -levent)
LIB_PCRE?=$(shell find /usr /opt -name libpcre.a 2> /dev/null | head -n 1 | grep . || echo -lpcre)
# Collect the list of libraries to link against (depending on the platform).
# These are the libraries used by the Crystal runtime.
CRYSTAL_RT_LIBS+=$(LIB_GC)
CRYSTAL_RT_LIBS+=$(LIB_EVENT)
CRYSTAL_RT_LIBS+=$(LIB_PCRE)
ifneq (,$(findstring macos,$(TARGET_PLATFORM)))
CRYSTAL_RT_LIBS+=-liconv
endif
ifneq (,$(findstring freebsd,$(TARGET_PLATFORM)))
CRYSTAL_RT_LIBS+=-L/usr/local/lib
endif
ifneq (,$(findstring dragonfly,$(TARGET_PLATFORM)))
CRYSTAL_RT_LIBS+=-L/usr/local/lib
endif
CRYSTAL_RT_LIBS+=-l$(LIB_CXX_KIND)
ifneq (,$(findstring dragonfly,$(TARGET_PLATFORM)))
# On DragonFly:
#
# * -flto=thin is not accepted
# * we have to explicitly state the linker
# * we cannot link libclang statically
SAVI_LD_FLAGS=-fuse-ld=lld -L/usr/lib -L/usr/local/lib -L/usr/lib/gcc80
LIB_CLANG=-lclang
else
SAVI_LD_FLAGS=-flto=thin -no-pie
LIB_CLANG=`sh -c 'ls $(LLVM_PATH)/lib/libclang*.a'`
endif
# This is the path to the Crystal standard library source code,
# including the LLVM extensions C++ file we need to build and link.
CRYSTAL_PATH?=$(shell env $(shell crystal env) printenv CRYSTAL_PATH | rev | cut -d ':' -f 1 | rev)
# Download the runtime bitcode library we have built separately.
# See github.com/savi-lang/runtime-bitcode for more info.
lib/libsavi_runtime: $(MAKE_VAR_CACHE)/RUNTIME_BITCODE_RELEASE_URL
rm -rf $@-tmp
mkdir -p $@-tmp
cd $@-tmp && curl -L --fail --retry 10 -sS \
"${RUNTIME_BITCODE_RELEASE_URL}/libsavi_runtime.tar.gz" \
| tar -xzvf -
rm -rf $@
mv $@-tmp $@
touch $@
# Download the static LLVM/clang libraries we have built separately.
# See github.com/savi-lang/llvm-static for more info.
# This target will be unused if someone overrides the LLVM_PATH variable
# to point to an LLVM installation they obtained by some other means.
$(BUILD)/llvm-static: $(MAKE_VAR_CACHE)/LLVM_STATIC_RELEASE_URL
rm -rf $@-tmp
mkdir -p $@-tmp
cd $@-tmp && curl -L --fail --retry 10 -sS \
"${LLVM_STATIC_RELEASE_URL}/${LLVM_STATIC_PLATFORM}-llvm-static.tar.gz" \
| tar -xzvf -
rm -rf $@
mv $@-tmp $@
touch $@
# Download the CapnProto compiler plugin for Savi code gen.
# See github.com/jemc-savi/CapnProto for more info.
$(BUILD)/capnpc-savi: $(MAKE_VAR_CACHE)/CAPNPC_SAVI_DOWNLOAD_URL
rm -f $@-tmp
curl -L --fail --retry 10 -sS "${CAPNPC_SAVI_DOWNLOAD_URL}" | tar -C $(BUILD) -xzvf -
chmod a+x $@
touch $@
# Build the CapnProto compiler plugin for Crystal code gen.
# See github.com/jemc/crystal-capnproto for more info.
$(BUILD)/capnpc-crystal: $(shell find lib/capnproto/src -name '*.cr')
crystal build lib/capnproto/src/capnpc-crystal/main.cr -o $@
# Build the Crystal LLVM C bindings extensions as LLVM bitcode.
# This bitcode needs to get linked into our Savi compiler executable.
$(BUILD)/llvm_ext.bc: $(LLVM_PATH)
mkdir -p `dirname $@`
${CLANGXX} -v -emit-llvm -g -gdwarf-4 \
-c `$(LLVM_CONFIG) --cxxflags` \
-target $(CLANG_TARGET_PLATFORM) \
$(CRYSTAL_PATH)/llvm/ext/llvm_ext.cc \
-o $@
# Build the extra Savi LLVM extensions as LLVM bitcode.
# This bitcode needs to get linked into our Savi compiler executable.
$(BUILD)/llvm_ext_for_savi.bc: $(LLVM_PATH) $(shell find src/savi/ext/llvm/for_savi -name '*.cc')
mkdir -p `dirname $@`
${CLANGXX} -v -emit-llvm -g -gdwarf-4 \
-c `$(LLVM_CONFIG) --cxxflags` \
-target $(CLANG_TARGET_PLATFORM) \
src/savi/ext/llvm/for_savi/main.cc \
-o $@
# Build the Savi compiler object file, based on the Crystal source code.
# We trick the Crystal compiler into thinking we are cross-compiling,
# so that it won't try to run the linker for us - we want to run it ourselves.
# This variant of the target compiles in release mode.
$(BUILD)/savi-release.o: main.cr $(LLVM_PATH) $(shell find src lib -name '*.cr')
mkdir -p `dirname $@`
env \
SAVI_VERSION="$(SAVI_VERSION)" \
SAVI_LLVM_VERSION=`$(LLVM_CONFIG) --version` \
LLVM_CONFIG=$(LLVM_CONFIG) \
LLVM_DEFAULT_TARGET=$(TARGET_PLATFORM) \
crystal build -Duse_pcre $< -o $(shell echo $@ | rev | cut -f 2- -d '.' | rev) \
--release --stats --error-trace --cross-compile --target $(TARGET_PLATFORM)
# Build the Savi compiler object file, based on the Crystal source code.
# We trick the Crystal compiler into thinking we are cross-compiling,
# so that it won't try to run the linker for us - we want to run it ourselves.
# This variant of the target compiles in debug mode.
$(BUILD)/savi-debug.o: main.cr $(LLVM_PATH) $(shell find src lib -name '*.cr')
mkdir -p `dirname $@`
env \
SAVI_VERSION="$(SAVI_VERSION)" \
SAVI_LLVM_VERSION=`$(LLVM_CONFIG) --version` \
LLVM_CONFIG=$(LLVM_CONFIG) \
LLVM_DEFAULT_TARGET=$(TARGET_PLATFORM) \
crystal build -Duse_pcre $< -o $(shell echo $@ | rev | cut -f 2- -d '.' | rev) \
--debug --stats --error-trace --cross-compile --target $(TARGET_PLATFORM)
# Build the Savi specs object file, based on the Crystal source code.
# We trick the Crystal compiler into thinking we are cross-compiling,
# so that it won't try to run the linker for us - we want to run it ourselves.
# This variant of the target will be used when running tests.
$(BUILD)/savi-spec.o: spec/all.cr $(LLVM_PATH) $(shell find src lib spec -name '*.cr')
mkdir -p `dirname $@`
env \
SAVI_VERSION="$(SAVI_VERSION)" \
SAVI_LLVM_VERSION=`$(LLVM_CONFIG) --version` \
LLVM_CONFIG=$(LLVM_CONFIG) \
LLVM_DEFAULT_TARGET=$(TARGET_PLATFORM) \
crystal build -Duse_pcre $< -o $(shell echo $@ | rev | cut -f 2- -d '.' | rev) \
--debug --stats --error-trace --cross-compile --target $(TARGET_PLATFORM)
# Build the Savi compiler executable, by linking the above targets together.
# This variant of the target compiles in release mode.
$(BUILD)/savi-release: $(BUILD)/savi-release.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc
mkdir -p `dirname $@`
${CLANG} -O3 -o $@ $(SAVI_LD_FLAGS) \
$(BUILD)/savi-release.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc \
${CRYSTAL_RT_LIBS} \
-target $(CLANG_TARGET_PLATFORM) \
`sh -c 'ls $(LLVM_PATH)/lib/liblld*.a'` \
`$(LLVM_CONFIG) --ldflags ` \
`$(LLVM_CONFIG) --libfiles --link-static` \
`$(LLVM_CONFIG) --system-libs --link-static` \
$(LIB_CLANG)
# Build the Savi compiler executable, by linking the above targets together.
# This variant of the target compiles in debug mode.
$(BUILD)/savi-debug: $(BUILD)/savi-debug.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc
mkdir -p `dirname $@`
${CLANG} -O0 -o $@ $(SAVI_LD_FLAGS) \
$(BUILD)/savi-debug.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc \
${CRYSTAL_RT_LIBS} \
-target $(CLANG_TARGET_PLATFORM) \
`sh -c 'ls $(LLVM_PATH)/lib/liblld*.a'` \
`$(LLVM_CONFIG) --ldflags ` \
`$(LLVM_CONFIG) --libfiles --link-static` \
`$(LLVM_CONFIG) --system-libs --link-static` \
$(LIB_CLANG)
if uname | grep -iq 'Darwin'; then dsymutil $@; fi
# Build the Savi specs executable, by linking the above targets together.
# This variant of the target will be used when running tests.
$(BUILD)/savi-spec: $(BUILD)/savi-spec.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc
mkdir -p `dirname $@`
${CLANG} -O0 -o $@ $(SAVI_LD_FLAGS) \
$(BUILD)/savi-spec.o $(BUILD)/llvm_ext.bc $(BUILD)/llvm_ext_for_savi.bc \
${CRYSTAL_RT_LIBS} \
-target $(CLANG_TARGET_PLATFORM) \
`sh -c 'ls $(LLVM_PATH)/lib/liblld*.a'` \
`$(LLVM_CONFIG) --ldflags ` \
`$(LLVM_CONFIG) --libfiles --link-static` \
`$(LLVM_CONFIG) --system-libs --link-static` \
$(LIB_CLANG)
if uname | grep -iq 'Darwin'; then dsymutil $@; fi