[LLVMdev] A better CMake

Talin viridia at gmail.com
Tue Dec 20 22:16:22 PST 2011


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...

-- 
-- Talin
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20111220/a262548f/attachment.html>


More information about the llvm-dev mailing list