irrelevant


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: [
    'warning_level=2',
    'c_std=gnu99',
    'werror=true',
  ],
)

add_project_arguments([
    '-D_GNU_SOURCE',
  ], 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=')
  add_project_arguments([
    '-fmacro-prefix-map=../=',
    ], language: 'c',
  )
endif

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 meson.build:

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'
  endif
endif

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

      # always choose a static version for embedded builds
      'default_library=static',
      ],
    required: true,
  )

  libfoo = libfoo_proj.get_variable('libfoo_dep')
endif

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: [
    find_program('scripts/ctags.sh')
  ])
endif

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