An opinionated Skeleton for meson projects

I admit it. I fell in love with meson at first sight. It is exactly the thing that I always wanted CMake to be. I have used it for a while, so I wanted to share a couple of tricks and best practices that I've picked up.

I start out with a pretty standard "preamble":

project('myproj', ['c'],
  version : '0.1',
  license : 'GPL-3.0-or-later',
  default_options: [

  ], language: 'c',

I like my GCC extensions, so naturally I set some friendly options for that.

Next up is something I think should be somehow baked into meson at some point. meson builds everything inside a dedicated build directory so when if we use stuff like __FILE__ in our debugging code we end up with paths like ../src. Let's get rid of that:

cc = meson.get_compiler('c')

# strip relative path prefixes from the code if possible
if cc.has_argument('-fmacro-prefix-map=prefix=')
    ], language: 'c',

I picked up this trick from sway, though they complicate it a bit.

Moving on, my projects usually have dependencies and more often than I would like, I vendor them. But I do like to be able to also pick them up as system libraries as well. I use meson_options.txt for this. Say I depend on libfoo. In meson_options.txt add:

option('libfoo', type: 'combo', value: 'auto',
  choices: ['auto', 'system', 'internal'],
  description: 'how to find the foo library')

This gives us a -Dlibfoo= switch that we can set to either auto, system or internal.

To actually take action on this, add the following in

libfoo_opt = get_option('libfoo')

if libfoo_opt in ['auto', 'system']
  libfoo = dependency('libfoo',
    # if auto, allow fallback to internal version
    required: libfoo_opt == 'system',

  if libfoo.found()
    libfoo_opt = 'system'
  else if libfoo_opt == 'auto'
    # fall back to internal version
    libfoo_opt = 'internal'

if libfoo_opt == 'internal'
  libfoo_proj = subproject('libfoo',
    default_options: [

      # always choose a static version for embedded builds
    required: true,

  libfoo = libfoo_proj.get_variable('libfoo_dep')

This utilizes the meson subproject functionality (it's pretty awesome).

At this point I typically start adding subdir()'s. It depends on your project if you want to split it, but I almost always split it up from the beginning.

Following any subdir()'s I add any run_target()'s, like generating ctags:

ctags = find_program('ctags', required: false)
if ctags.found()
  run_target('ctags', command: [

scripts/ is a small wrapper around invoking ctags for several directories.