A better CMake

I’ve been using CMake on my LLVM project for several years now (ever since I abandoned using SCons, and before that, autoconf). During that time I’ve grown to both love and hate CMake - that is, I love the idea, but hate the language and its limitations.

However, due to the recent thread on this list, I realized that I wasn’t the only person that felt that way - that there were a lot of folks who hated the CMake language just as much as I did. And after thinking about this for a while, I came to the conclusion that “I bet I could do better”.

Of course, this is not the first time I’ve considered writing my own build system - I have notes going back quite a few years on this subject, which I first started thinking about while working at Electronic Arts - one of the tools I wrote there was a Python script for generating Visual Studio project files. (And yes, I’ve looked at almost every other build system out there - I’ve even used NAnt…and Jam before it was part of Boost…)

In any case, I decided to take a vacation from working on Tart, and instead work on this new thing I call “Mint” (think of minting a coin - it’s a synonym of “make”). It’s been about six weeks now, and I now have something that is able to perform configuration tests, generate its own config.h file, rebuild itself (along with google test and RE2) - and does so concurrently. (It’s currently hard-coded at 4 subprocesses, but I plan to make that a parameter.) I’m still working on automatic dependency generation, as well as generation of makefiles and IDE project files. (At the moment I’m in the middle of adding support for exporting the build configuration as XML, so that other tools can easily be written to operate on a build tree.)

One big advantage over CMake is that all of the “knowledge” of how to invoke the C++ compiler isn’t hard-coded, but is expressed in terms of the Mint build language, which is a hybrid declarative/functional language for describing build dependencies and actions. Compilers like clang and gcc are simply objects in the Mint standard prelude - here’s what the ‘clang’ object currently looks like:

-----------------------------------------------------------------------------

Definitions for the clang C/C++/Objective-C compiler

-----------------------------------------------------------------------------

from compiler import compiler, linker

Note these objects aren’t intended to be invoked directly, it’s usually

a target object which instantiates this and fills in all of the params.

clang = {

clang, when used as a compiler

‘compiler’ = compiler { # Inherit from generic compiler prototype

Inputs

param flags : list[string] # Compiler-specific flags

param include_dirs : list[string]

param sources : list[string]

param outputs : list[string]

param source_dir : string

param warnings_as_errors : bool # Generic compiler flags

param all_warnings : bool # "

Outputs

compile => [ # ‘=>’ means dynamic evaluation

message.status(“Compiling ${sources[0]}\n”)

command(‘clang’,

[’-c’] ++

(all_warnings and [ ‘-Wall’ ]) ++

(warnings_as_errors and [ ‘-Werror’ ]) ++

flags ++

Include dirs by default are relative to source root

Syntax for closures is ‘x => expr(x)’

include_dirs.map(x => [’-I’, path.join(source_dir, x)]).merge() ++

[’-o’, outputs[0]] ++

path.join_all(source_dir, sources))

]

},

clang, when used as a linker

‘linker’ = linker { # Inherit from generic linker prototype

Inputs

param flags : list[string]

param lib_dirs : list[string]

param libs : list[string]

param sources : list[string]

param outputs : list[string]

param warnings_as_errors : bool

param all_warnings : bool

Outputs

build => [

message.status(“Linking program ${outputs[0]}\n”)

command(‘clang’,

(all_warnings and [ ‘-Wall’ ]) ++

(warnings_as_errors and [ ‘-Werror’ ]) ++

flags ++

libs.map(x => ‘-l’ ++ x) ++

Lib dirs are relative to build dir by default

lib_dirs.map(x => [’-L’, x]).merge() ++

[’-o’, outputs[0]] ++

sources)

]

}

TODO: clang, when used as gendeps

}

This lack of hard-codedness means that it’s relatively straightforward to support new compilers and new languages. Here’s another short example, this is from the configuration test that can be used to detect whether a given struct has a particular member:

Now I don’t want to generate too much traffic on this list, because I realize that this isn’t really LLVM-related, other than the fact that the initial impetus came from here. If it turns out that people are interested in this, I’ll have to find some sort of appropriate forum for this…

In any case, what I mainly want at this point is criticism of the design. There are docs here: https://github.com/viridia/Mint/wiki/Mint-Introduction , and the source is browsable here: https://github.com/viridia/Mint. I’m interested in any and all feedback…