Abstract
In this post, I want to give a little more details on how Alisp can build from sources. ALisp is an interpreted language I’ve developed in C++. Over the last half a year, the project has become quite big (currently around 24K LOC) and thus the build process is so trivial as in the beginning. In the process of development, I’ve added numerous options for how the code can be compiled and all of those can be confusing at times. Depending on the purpose of the build and what you want to do with it, different options have to enabled and here I want to give a brief overview of the options and the reasoning behind them.
Prerequisites
Alisp is written in C++ (17) and uses CMake as its build generator. The project is tested and it compiles with
- gcc 8,9
- clang 9
Currently, only Linux builds are tested and I can’t guarantee that everything will work on Windows (hopefully supports comes soon). The code requires features of C++17 so it may not be compatible with older compilers.
On Debian based systems, for minimal builds the package build-essential should be enough:
sudo apt-get install build-essential
For more advanced builds, you’ll need more things:
sudo apt-get install valgrind gcov doxygen
Doxygen is used to generate the documentation for the source, valgrind performs some run-time checks and gcov.
Optionally, the build setup can take advantage of ccache if you have on your system. This is a compiler cache that can reduce compilation times. For Debian systems, to install ccach, you can run:
sudo apt-get install ccache
The code itself should not have any external dependencies. The project’s repository contains everything needed for building all executables and libraries of the language form source. Alisp does, however, uses third party C++ libraries and those are:
- Catch2 - used for testing
- Clipp - used for command line arguments parsing
- Rang - used for printing in colors on the terminal
- TinyXML2 - used for parsing xml documents
- Subprocess - used for spawning child processes
- SimpleJSON - used for parsing json documents.
- Fmt - used for formatting strings
Building
Once you have everything you need, you can proceed to the actual build process. The way I usually do this is to create a build folder in the source directory and build the project there. The build commands are standard for any CMake project.
git clone https://github.com/palikar/alisp
cd alisp
mkdir build && cd build
cmake .. <options>
make <targets>
Optionally, after cloning you can checkout the project to any of the current stable version.
<options>
and <targets>
are, of course, placeholder and will be discussed in the next section.
Options
As shown, options are given when executing the CMake command. The format is -D<OPTION>=<VALUE>
. For example, DOPTION_1=TRUE
sets the option OPTION_1
to TRUE
.
First, we’ll go over the project-specific options. Those have been declared in the CMakeList.txt file with the option function of CMake. Next, I’ll also mention a couple of other general CMake options that are commonly used.
Project-specific options
-
LIBCPP
- when compiling with clang, this option allows you to use llvm’s implementation of the C++ standard library libc++. In certain situations, libc++ can improve the performance of a program and reduce the compilation time. For the majority of the development of Alisp, however, I’ve used GNU’s standard library libstdc++ and I haven’t got any problems with it. If theLIBCPP
option is set toTRUE
(defaultFALSE
), libc++ will be used. -
ENABLE_COVERAGE
- if set toTRUE
(defaultFALSE
), the project will be compiled with coverage flags (--coverage -O0 -g
) and special make targets will be created that allows you to run coverage of the code and generate nice HTML pages with the results. -
ENABLE_ASAN
- if set toTRUE
(defaultFALSE
), the project will be compiled with address sanitizer flags (-fsanitize=address
). When an executable is built with address sanitizer, running it will execute the program while checking for any memory issues (buffer overflows, invalid reads/ writes, etc.) More information on address sanitizers here.
-
BUILD_SHARED_LIBS
- if set toTRUE
(defaultFALSE
), all of the libraries will be built as shared libraries. This usually reduces the recompilation time but may cause some overhead on startup. By now, I use Alisp almost exclusively in shared libraries mode. -
ENABLE_TESTING
- if set toTRUE
(defaultFALSE
), all of the tests for the project will be compiled and build. Also, several make targets will be created for running the added tests. -
ENABLE_CLANG_TIDY
- if set toTRUE
(defaultFALSE
), clang-tidy will be run over the source code during compilation. Clang-tidy is a code linter and static analyzer and can report on some common problems with the code. -
ENABLE_CPPCHECK
- if set toTRUE
(defaultFALSE
), cppcheck will be run over the source code during compilation. Cppcheck is a static analysis tool for C/C++ code that detects bugs and focuses on detecting undefined behavior and dangerous coding constructs. -
ENABLE_LTO
- if set toTRUE
(defaultFALSE
), the project is compiled with link time optimizations enabled. -
BUILD_DOC
- if set toTRUE
(defaultFALSE
), a special make target is created that will generate documentation of the source code with Doxygen. -
FORCE_COLORED_OUTPUT
- if set toTRUE
(defaultTRUE
), the output of the compilation will use coloring. This is handy when compiling and looking at the output on the screen. It could be annoying when the output of the compilation is dumped into a file. -
ELF_HASHING
- if set toTRUE
(defaultFALSE
), the default hashing algorithm that Alisp uses internally will be the PJW hash function. By default, Alisp uses the Fowler–Noll–Vo function for hashing strings. -
JENKINS_HASHING
- if set toTRUE
(defaultFALSE
), the default hashing algorithm that Alisp uses internally will be the Jenkins hash function. If this option isTRUE
,ELF_HASHING
should be set toFALSE
. -
DEBUG_LOGGING
- if set toTRUE
(defaultFALSE
), the project will be compiled with support for enabling lots of logging. The interpreter will have an extra CLI flag (-DL
) that will print lots of debugging information during execution. -
BUILD_EXAMPLES
- if set toTRUE
(defaultFALSE
), the example scripts in thescripts
folder will be copied in the build directory. Also, an extra make target will be created (test_examples
) that will execute all of the examples and verify they execute without errors. -
VALGRIND_CHECKS
- if set toTRUE
(defaultFALSE
), an extra make target (valgrind_checks
) will be created that executes the language tests while checking the execution with various valgrind tools. This includes checking for memory leaks, running the tests with massif, memcheck and callgrind. -
RUN_PERFORMANCE_TESTS
- if set toTRUE
(defaultFALSE
), the scripts in theperformance_tests
will be copied in the build directory and an extra make target will be created (timing_check
). The target will execute the copied scripts and will create a filetiming_checks.txt
with their execution time. The scripts are designed in such a way to load the interpreter and test its performance. -
DISABLE_DYN_MODULES
- if set toTRUE
(defaultFALSE
), the dynamic Alisp modules will not be compiled. Those are modules that are not loaded in each interpreter executable but are loaded dynamically at run time. Disabling the compilation of those can be handy when you don’t want to compile the entirety of the project but just the core interpreter. -
DISABLE_DEFAULT_MODULES
- if set toTRUE
(defaultFALSE
), the builtin Alisp modules will not be compiled and linked to the interpreter. The point of this is again to reduce the compilation time if you don’t need these modules. -
ENABLE_STACK_TRACE
- if set toTRUE
(defaultTRUE
), the interpreter will keep track of each function call and will print out a stacktrace in case of an error in the execution. Disabling this option can potentially increase performance. -
ENABLE_LINE_TRACE
- if set toTRUE
(defaultTRUE
), the interpreter will keep track of the line number where a list-form object was defined in a file. This makes the stracktrace more understandable but also requires a tiny bit more memory. The line numbers will be saved as object properties (--line--
) of list-forms that appear in the source code of an Alisp script. -
ENABLE_OBJECT_DOC
- if set toTRUE
(defaultTRUE
), the interpreter will keep the documentation for symbols. If this option is disabled, the given in the source code documentation for the symbols will be left out and won’t be part of the symbols. The documentation for a symbol is saved in the--doc--
property. -
DISABLE_RUN_TIME_CHECKS
- if set toTRUE
(defaultFALSE
), a lot of run-time checks (among which arity checks by the function calling and type checks) will be removed from the code. This can potentially increase performance but will make the interpreter extremely unstable. Segmentation faults are to be expected if this option is enabled. It is, however, guarantee that a valid program in Alisp will run correctly if these checks are disabled. -
TIME_CHECK_SAMPLES
- this is an integer value (default 10) that signifies how many times the performance test scripts should be run to get a stable average value. -
ENABLE_MTEL
- if set toTRUE
(defaultFALSE
), the project will be built with a multi-threaded event loop. Usually, the event loops run on a single thread that does all of the asynchronous dispatching. With this option, multiple threads will be used to handle asynchronous events and callbacks.
General Options To Keep in Mind
-
CMAKE_INSTALL_PREFIX
- this is a path indicating where the compiled libraries and executable should be installed (copied to when everything is built) throughmake install
. On Linux systems, this is usually/usr
but I often change this to some localusr
directory. Executable will be put inCMAKE_INSTALL_PREFIX/bin
, libraries inCMAKE_INSTALL_PREFIX/lib
and header files inCMAKE_INSTALL_PREFIX/include/alisp
. -
CMAKE_BUILD_TYPE
- string variable indicating the type of build that is to be built. It can be “Debug”, “Release”, “RelWithDebInfo” or “MinSizeRel”. In most cases, I use either “Debug” (debug information is compiled in) or “Release” (the compilation is optimized fully and there is no debug information).
Make Targets overview
With the options being enplaned now, here I’ll give a brief overview of all possible make targets. Those can be run after Cmake has executed, build files are ready and we can proceed to compilation and testing.
-
all
- compile the project -
test
- run all of the tests. The project has to be built with-DENABLE_TESTING=TRUE
-
alisp_coverage_html
- execute the tests, gather coverage information, and generate a web site with the results. The project has to be built with-DENABLE_COVERAGE=TRUE
-
alisp_coverage_xml
- execute the tests, gather coverage information, and generate a web site with the results. The project has to be built with-DENABLE_COVERAGE=TRUE
-
doc
- build the documentation of the source code -
install
- copy the compiled executable, libraries, and public headers at the appropriate places. -
package
- package the project files with CPack. This will generate .deb and .tar.gz files. -
test_examples
- execute all of the examples in thescripts
directory. The project has to be built with-DBUILD_EXAMPLES=TRUE
-
valgrind_checks
- execute./bin/alisp_language_test
with valgrind for all of the defined valgrind checks. The project has to be built with-DVALGRIND_CHECKS=TRUE
-
timing_check
- execute the scripts that test the performance of the interpreter. The project has to be built with-DRUN_PERFORMANCE_TESTS=TRUE