Note: most of this article is simply an incomplete natural language narration of what Crystal's Makefile specifies. If you, like me, feel a bit intimidated by Make, you may find this article more welcoming than venturing into the code by yourself and figuring things out from scratch.
At the root level of crystal-lang/crystal there's a Makefile that triggers the build process.
The default task in that Makefile is crystal.
That task checks whether there's a .build/crystal executable.
To build the executable, you first need its dependencies (DEPS) and the source code (SOURCES).
With the dependencies built and the list of source code files, we are ready to compile Crystal, by running a previous version of the compiler that sits at bin/crystal. The new compiler will be written to .build/crystal.
The compiler receives a single input file: src/compiler/crystal.cr. We'll go over that in another article. For now, the key takeaway is that this file is the entry point to everything you can do by running crystal [...].
By default, we build the compiler without OpenSSL (without_openssl flag) and without zlib (without_zlib) flag. We'll leave Crystal OpenSSL and Zlib how-to's for future articles.
There are two dependencies at this level: an LLVM object file that we use to consume LLVM services directly from Crystal (llvm_ext.o) and a static Crystal library (libcrystal.a) which varies depending on the target system.
We build the LLVM extension object file by compiling src/llvm_ext.cc, which is a C++ file. The C++ compiler is chosen via the CXX variable. Compiler flags are switched via CXXFLAGS. To discover which version of LLVM we have in the system, we shell out and try different ways of calling llvm-config and its variants until one succeeds.
We build this target-dependant static lib by compiling all .c files found at src/ext(LIB_CRYSTAL_OBJS).
Source code is simply every *.cr file under the src directory.