Introduction
This is the official documentation for Muon, a meson-compatible build system. Most of the topics discussed are muon-specific.
- For general Meson documentation refer to the Meson User Manual.
- For documentation on functions and objects refer to the Muon Reference.
Features
In addition to trying maintain Meson compatibility, Muon also has a few features of its own.
muon analyze
muon has a full-featured static analyzer for meson build scripts. It can check for undefined variables, unused variables, variable reassignment to different types, undefined functions, undefined kwargs, missing required args, methods, operator typechecking, and more.
See muon analyze -h for more information.
muon fmt
A formatter for meson build scripts.
See muon fmt -h for more information.
Debugger (WIP)
Muon has an interactive gdb-like debugger. You can trigger it by setting breakpoints with the -b flag on either setup or internal eval subcommands. Inside the debugger you can look around, evaluate expressions, and step through code.
Built-In Ninja (samurai)
Muon has an embedded copy of samurai
(at src/external/samurai) which has been ported to use muon's cross-platform
layer and so works on all the platforms muon supports. This means you can just
grab a single muon binary and that's all you need to build your project.
VM
Muon's language runtime is a fully featured stack-based VM. All scripts are compiled to bytecode before execution. This runtime is powerful enough to host modules as well as basic cross-platform scripting. See Scripting for more information
Fast
Muon is orders of magnitude faster than meson when your scripts are interpreter- bound. This means that as long as muon doesn't have to shell out to the compiler it is very fast.
Typically the most speedup can be seen during re-configs where most compiler checks have been cached.
Differences Between Muon and Meson
This document describes functional differences between muon and Meson. None of these is a set-in-stone design decision, just a reflection of the current state of affairs. This document is also not exhaustive, but is a best-effort list.
Some other small differences may be found by searching the tests/project for "# different" comments.
nested subproject promotion
Meson performs nested subproject promotion. This means that nested subprojects become top-level subprojects, and all subprojects share the same namespace.
For example, given the following project structure:
.
├── meson.build
└── subprojects
├── a
│ ├── meson.build
│ └── subprojects
│ └── b
│ └── meson.build
└── b
└── meson.build
The order of subproject calls determines which subprojects will be used:
project('main')
# This causes all subprojects under subprojects/a/subprojects/ to be "promoted"
subproject('a')
# This will now use subprojects/a/subprojects/b, instead of subprojects/b
subproject('b')
muon does not perform subproject promotion.
malformed escape sequences
Meson silently accepts malformed escape sequences and outputs them literally, removing the leading escape character. For example:
'\c' # becomes 'c'
'\Uabcdefghi' # becomes 'Uabcdefghi'
'\xqr' # becomes 'xqr'
In muon, malformed escape sequences are parse errors.
format strings
Format strings in various parts of Meson use @ as the delimiter. The behavior
is inconsistent, configure_file() recognizes \ as an escape character, but
format strings in string.format() and custom_target command arguments do
not. Muon will accept \ as an escape character in string.format() and
custom_target, but this is incompatible with meson. \ use in
custom_target command arguments is already incompatible though (see next
point).
configure_file() will also warn you about invalid substitutions, and will
remove them in the output, string.format() will error on invalid
substitutions, and custom_target command arguments will be silently treated as
literals if they are invalid substitutions (e.g. @BAZ@).
Because some projects rely on the above custom target command argument behaviour, muon merely adds a warning for invalid substitutions. In all other cases muon will raise error.
custom_target replaces backslashes with slashes in the command arguments
In Meson, all backslashes in custom_target command line arguments are blindly
replaced to forward slashes. This behavior is not present in muon.
Reference: https://github.com/mesonbuild/meson/issues/1564
build_target() functions with empty sources
Meson allows you to create build targets (executable(), shared_library(),
static_library(), etc.) without specifying any sources. In muon this is an
error.
global compiler cache
Meson maintains a global compiler cache, which means that all languages added by subprojects are available to the main project and vice-versa. This can hide bugs that will surface if the subproject is built by itself, or subproject calls are rearranged. muon also maintains a global cache, but requires subprojects to still call add_languages to access this cache.
backslash escaping in compiler defines
Meson replaces \ with \\ in compiler defines. muon does not. This is
legacy behavior that prevents you from using things like C escapes (e.g. \n)
in compiler defines, at the benefit of making it easier to use windows paths.
See meson commit aca93df184a32ed7faf3636c0fbe90d05cb67857 for more information:
Jon Turney: Now that all command-line escaping for ninja is dealt with in the ninja backend, escape_extra_args() shouldn't need to do anything.
But tests of existing behaviour rely on all backslashes in defines being C escaped: This means that Windows-style paths including backslashes can be safely used, but makes it impossible to have a define containing a C escape.
default option values
A number of builtin options have different default values:
default_libraryis'static'
native: true
In muon native: true always means "for the build machine", while in meson
sometimes it doesn't.
Meson distinguishes between cross and native builds. A native build is one where the build machine and the host machine are the same, while a cross build is one where they differ. Since this is a build system, "machine" in this context is mostly synonymous with "toolchain for a machine".
A nice cross compilation feature that meson supports is the ability to toggle
which toolchain a particular target uses at configure time. This is
accomplished by using the native keyword available for many functions. Passing
native: false causes the target/dependency to be created/looked up for the
host machine. Likewise, native: true causes the target/dependency to be
created/looked up for the build machine. In most cases omitting the native
keyword is the same as passing native: false, although some functions treat
the omission of native specially, such as add_languages adding languages for
both the host and build machine if the keyword is omitted.
The above paragraph is true for muon too, but it is actually subtly wrong for
meson. You might be wondering, why meson uses native: true/false and not
something more readable such as for_machine: 'build'/'host'. The reason is
that during a native build, meson effectively ignores the native keyword.
While this makes sense on some level since the machines you are selecting
between are the same, it confuses the usage of the native keyword even more
than just the odd mapping from boolean to machine, and makes it easier to create
a build file that is broken for cross compilation.
For example (taken from meson's own tests), the following code is perfectly valid during a native build:
meson.override_dependency('expat', declare_dependency())
dependency('expat')
dependency('expat', native : true)
dependency('expat', native : false)
But will break with a dependency not found error during a cross build.
largefile source
Meson always adds -D_FILE_OFFSET_BITS=64 to the list of compile arguments
for all targets on certain compiler/platform combinations. muon does not pass
this by default, but can match meson's behavior if the b_largefile option is
set to auto. The muon meson wrapper automatically passes
-Db_largefile=auto.
Contributing
Thanks for considering contributing to muon. Please send patches and questions to ~lattis/muon@lists.sr.ht. Alternatively you may make a pull request at https://github.com/muon-build/muon. Before making any big changes, please send a proposal to the mailing list or open an issue so I can give you some pointers, and make sure you don't waste your time.
Contribution areas
There are two main ways to contribute to Muon:
- Add a script module. There are several
unimplemented modules that could likely be easily written as a script module.
See
src/script/modulesfor examples. - Work on the runtime. Please read the documentation on Muon Internals.
C Style
Muon uses a style similar to the linux kernel. A few differences are:
-
the return type of a function goes on the line above its declaration. e.g.
int main(void) { return 0; } -
it still goes on the same line in a function prototype e.g.
int main(void); -
never omit braces for single statement if, else, for, while, etc.
-
avoid function-like macros except in exceptional cases
-
it is OK (but not great) if your line is more than 80 characters
-
please use fixed-width integer types (e.g.
uint32_t,uint64_t,uint8_t) whenever possible
As usual, just try to follow the style of surrounding code.
Scripting
Muon supports modules and small scripts written in meson. In order to facilitate this, various language features are enabled during module and script evaluation.
Evaluation Modes
Muon's implementation of the meson language includes three evaluation modes:
external: Regular meson.build files external to muon's implementation are evaluated in this mode.internal: Any meson file evaluated usingmuon internal eval ...is evaluated in the internal mode.extended: A layering of internal on top of external, allows features from both modes to be used simultaneously. This is the mode that script modules execute in.
Both internal and extended mode enable the same additional language features
collectively referred to as scripting mode. The primary difference between the
two modes lies in which functions may be called. In particular, most functions
that require a project() or manipulate the build state in any way are not
available in internal mode.
Scripting Mode
Muon's scripting mode consists of several langauge features as well as additional functions and modules.
This guide focuses on the language features. The additional functions and modules are documented in the Muon Reference. All functions, modules, and arguments are tagged with their availability in muon's different evaluation modes.
Mutability
In scripting mode mutability rules are relaxed slightly. list and dict
types may be mutated by reference.
# Mutating a list with + and +=:
list = [0, 2, 3]
list[0] = 1
assert(list == [1, 2, 3])
list[0] += 1
assert(list == [2, 2, 3])
# Mutating a dict with + and +=:
dict = {'a': 1}
dict['a'] = 2
assert(dict == {'a': 2})
dict['a'] += 1
assert(dict == {'a': 3})
dict['b'] = 4
assert(dict == {'a': 3, 'b': 4})
Dictionaries may also be indexed using the . operator in scripting mode so the
above example could be rewritten as:
# some asserts removed
dict = {'a': 1}
dict.a = 2
dict.a += 1
dict.b = 4
assert(dict == {'a': 3, 'b': 4})
Re-assigning creates a copy1, mutations do not affect the copied variables.
list = [0, 1]
list2 = list
list[0] += 1
assert(list == [1, 1])
assert(list2 == [0, 1])
Muon implements this using copy-on-write but the effect should be the same.
Function declaration
Function definition is started with the func keyword optionally followed by an
identifier, and a list of arguments enclosed in parenthesis. If the function
returns a value it must specify the type of that value by adding -> <type>
after the argument list.
- The identifier is required if the function definition is a statement.
- The identifier is not allowed if the function definition is in an expression.
Positional and keyword arguments are supported in the argument list. Positional arguments must come first. Keyword arguments are specified by adding a colon (:) to the end of the argument followed by an optional default value. All arguments must specify their type by following the argument name with a type specifier.
Function definition is ended with the endfunc keyword.
Examples:
func a() -> int
return 1
endfunc
func b(arg1 int) -> int
return arg1
endfunc
func c(arg1 int, kw str:, kw_with_default str: 'default')
message(arg1)
message(kw)
message(kw_with_default)
endfunc
d = func()
message('hello from an anonymous function!')
endfunc
Scope
Modules have their own scope and cannot access any external variables (save builtins such as meson, build_machine, etc.).
Functions also get their own scope, but can capture variables that have been declared in parent scopes prior to the function definition.
Variable declaration implicitly occurs the first time it is assigned. To force
a local declaration (e.g. to shadow a variable) you may use the := syntax.
For examples see tests/lang/scope.meson.
Type Specifiers
Type specifiers consist of primitive types with optional subtypes.
Supported primitive types are listed in the Muon
Reference under the Objects header.
A value that can take one of many types can be specified by writing multiple
types separated with a | such as int|str.
There are a few preset combinations of types:
any- all types exceptnullexe-str|file|external_program|python_installation|build_tgt|custom_tgt
dict and list must be followed by a sub-type enclosed in square
brackets, e.g. dict[str|int] or list[any].
You can also wrap the entire type in glob[<type>] or listify[<type>] to get
the special argument handling detailed in Muon Internals (search for
TYPE_TAG_{GLOB,LISTIFY}).
Applying the glob tag to the type of a kwarg causes any unmatched keywords to
be stored in that kwarg as a dict.
Function behavior changes
This document lists all modifications to function behavior when a function is called during scripting mode vs external mode.
- In regular meson functions which create targets, the
outputparameter is not allowed to contain file separators. This has the effect that targets must only produce outputs which live in the current build directory. In extended meson file separators are permitted in these cases and so extended meson functions can create targets whose outputs may exist in other build directories.
Module Structure
A script module consists of functions that perform the various module
operations. The final return at module scope is what "exports" the functions
to module consumers. The returned object must be of type dict[func].
func func1()
endfunc
func func2()
endfunc
return {
'func1': func1,
'func2': func2,
}
Using some of the new syntax for dict mutation we can define modules like this
too:
M = {}
M.func1 = func()
endfunc
M.func2 = func()
endfunc
return M
Adding a new builtin script module
After you have written your script module you must register it so it can be imported.
All script modules live at src/script/modules/<module_name>.meson. After
adding the module, you must modify src/script/meson.build to register it. muon
will embed the module source text in the built executable, and when the module
is import()ed that source will be interpreted.
Rationale
Meson doesn't support user defined functions as one of its core principles. Why would muon relax this limitation? Partially because I believe that careful implementation of user defined functionality would not be harmful. We have seen meson scripts can get plenty complex anyways. The main reason however, is that requiring every last bit of functionality to be implemented in the build system itself is fundamentally untenable, especially if the build system will support multiple implementations.
- Many meson modules are effectively reusable chunks of meson code (calls to dependency, custom_target, etc.), so it is not necessary to access things that a native module has access to like the interpreter state.
- Lowering the bar for module contributions (this is important because they often involve specific tools I have no experience with/interest in).
- Potential to some day share module code with other meson implementations.
That last point is a bit pie in the sky, but at least this makes it technically possible from muon's point of view and proves that it has value.
Muon Internals
Error handling
All errors that can be checked, should be checked. If an error is detected, an
error message should be printed using vm_error, or if there is no source
code associated with the error, LOG_E. The error should be immediately
returned. Most functions returning a bool return false on error. The most
common other type of error returning function has the return type
enum iteration_result. These functions should return ir_err on error.
Meson functions
All meson functions are defined in functions with a separate file for each
object type on which the function is defined. Functions not defined on an
object are in kernel.c. If the function implementation is sufficiently large,
it may be broken up into a separate file under functions/<object type>/<function name>.c.
When declaring a new function, you need to add it to the "impl_tbl" at the
bottom of the file. All functions should call pop_args() before they do
anything, even if they don't take any arguments. pop_args takes 3 arrays,
conventionally named an (args, normal), ao (args, optional), and akw
(args, keyword). Any of these may be NULL. In particular, if they are all NULL
then the function takes no arguments, and pop_args will ensure this is the
case.
Arguments should specify what types they accept by bitwise or-ing tc_-prefixed
types together. an and ao argument arrays must be terminated by an
argument of type ARG_TYPE_NULL.
TYPE_TAG_GLOB can be or-d with the type of the last element of an, and will
store the remaining arguments in an obj_array. This is similar to e.g. def func(a, b, *c): in python, where c is the "glob" argument.
You may also bitwise or any type with TYPE_TAG_LISTIFY. This "type" will
cause pop_args to do the following things:
- coerce single elements to arrays
'hello' #=> ['hello']
- flatten arrays
['hello', [], [['world']]] #=> ['hello', 'world']
- typecheck all elements of the array
- given the "type"
TYPE_TAG_LISTIFY | obj_string, the above examples would pass, but['a', false]would not.
- given the "type"
Workspace
The workspace is a structure that contains all the data for an entire build setup, including all subprojects, AST, options, objects, strings. Most interpreter related functions take a workspace as one of their arguments.
Objects
Meson objects are created with the make_obj function. See object.h for more
information. Conventionally, all objects are referred to by id of type obj
rather than a pointer.
Memory
You may be wondering, what about Garbage collection? Muon's current approach is to cleanup only once at the very end. Meson is not a Turing-complete language, so we don't need to worry about long running programs.
Release Notes
0.8.0
- improve support for building and testing muon using meson
- support b_lto_threads
✓ 0.7.0
released on 2026.10.04
- more cross file support
- add a way to disable docs
- set more muon meson defaults to improve drop in behavior
- fully implement meson.add_devenv
- improvements to rpath fixer
- improve haiku support
- bump meson support to 1.11
- 1.8.0
- New argument android_exe_type for executables stub
- Changes to the b_sanitize option
- i18n module xgettext stub
- version_compare now accept multiple compare strings
Improvements to Objective-C and Objective-C++- Per project subproject options rewrite*
- Note: internals are done, but some questionable edge cases such as supporting -U and -A, or setting project options without yielding them using -D:foo=bar syntax are not implemented.
- 1.9.0
- Array .flatten() method
Support response files for custom targets(postponing for now, also unclear why this would be needed since custom_targets already have rsp-like behavior usingmuon internal exe -a)- Added license keyword to pkgconfig.generate
- pkgconfig.generate supports internal dependencies in requires
- 1.10.0
- Support for the counted_by attribute
- Added a values() method for dictionaries
- Add cmd_array method to ExternalProgram
- Added OS/2 support (shortname kwarg / os2_emxomf builtin opt) stub
- Array .slice() method
- -Db_vscrt on clang
- Added build_subdir arg to various targets
- Methods from compiler object now accept strings for include_directories (already supported)
- Using meson.get_compiler() to get a language from another project is marked (already an error)
- Add a configure log in meson-logs (already supported)
- Added new namingscheme option
- New method to handle GNU and Windows symbol visibility for C/C++/ObjC/ObjC++
- 1.11.0
- BuildTarget(install_dir) length > 1 replaced with keywords
- Deprecate should_fail and rename it to expected_fail, also introduce expected_exitcode
- install_man and install_headers: add support for install_tag kwarg
- Added link_early_args to targets performing linking
- Machine files now expand ~ as the user's home directory
- windows.compile_resources now detects header changes with rc.exe
- Added implicit_include_directories argument to windows.compile_resources
- Console kwarg on run_command
- 1.8.0
- 10 contributors:
- Daksh Mamodiya (3)
- Jan200101 (1)
- Juri Sacchetta (2)
- Ross Burton (1)
- Stone Tickle (137)
- VaiTon (1)
- kzc (11)
- moi15moi (7)
- tox (1)
- uni-dos (1)
✓ 0.6.0
released on 2026.06.21
- internals / language features
- in script mode: refactor how scope works, all variable resolution happens at compile time, globals are disallowed. Improves performance and clarifies scoping rules. (github pr)[https://github.com/muon-build/muon/pull/241]
- all memory is tracked an managed with an arena allocator. This should improve memory usage, performance, and developer ergonomics.
- toolchains
- toolchains are now defined in scripts:
src/script/runtime/toolchains.meson - tcc
- clang-cl
- ccache and sccache
- toolchains are now defined in scripts:
- documentation
muon help- source links in reference manual
- tooling
- DAP debugger protocol support & dcmake integration github issue
- wayland module
- support cross/native files
- 12 contributors:
- Christopher Wellons (1)
- Daniel Wagner (1)
- Fredrik Foss-Indrehus (1)
- kzc (20)
- Michael Forney (3)
- Sertonix (1)
- Stone Tickle (544)
- Teselka (9)
- VaiTon (14)
- Vincent Torri (2)
- vtorri (1)
- Zephyr Lykos (1)
✓ 0.5.0
released on 2025.07.28
- muon analyze now has an LSP mode
- pkg-config-exec backend added. muon now supports shelling out to pkg-config (or pkgconf).
- bump meson support to 1.7
- more builtin dependency handlers
- lots of bug fixes
- 9 contributors:
- Jonathan Schleifer (3)
- LoneFox78 (1)
- Michael Forney (1)
- NRK (6)
- Stone Tickle (416)
- VaiTon (3)
- Vincent Torri (2)
- kzc (4)
- m-hugo (1)
✓ 0.4.0
released on 2025.02.12
- More windows improvements
- only ~30 tests failing with msvc
- vsenv ported from meson
- .exe deployed by CI
- Coverage targets are now supported thanks to Andrew McNulty
- Various default compiler options were brought in line with meson thanks to Michal Sieron
- XCode backend
- Improvements/bug fixes for script modules
- gnome module improvements thanks to sewn.
- dependency() overhaul, now more closely matches meson's implementation
- Custom dependency handlers can be defined in script mode.
docs/*.mdported to mdbook and hosted at https://docs.muon.build- A reference manual containing all the
functions, modules, objects, and methods that muon knows about is
auto-generated on every build.
- It also generates documentation for script modules using doc comments!
- An experimental UI
- As always, lots of bugs fixed!
- 12 contributors:
- Andrew McNulty (1)
- Arsen Arsenović (1)
- Eli Schwartz (1)
- Jonathan Schleifer (1)
- Jürg Billeter (1)
- Michael Forney (2)
- Michal Sieron (4)
- Stone Tickle (303)
- Theo Paris (1)
- Vincent Torri (3)
- kzc (1)
- sewn (4)
✓ 0.3.0
released on 2024.09.24
- Brand-new interpreter: https://mochiro.moe/posts/10-muon-internals/
- Lots of windows improvements
- bootstrap.bat
- Simple tests passing
- Lots of macOS improvements
- All tests passing
- Universal binary deployed by CI
- Script modules introduced: a way of writing muon modules with mostly normal
meson code.
- i18n module
- gnome module (wip)
- Embedded cross-platform samurai implementation
- Lots of bugs fixed!
- 17 contributors:
- Andrea Pappacoda (1)
- Andrew McNulty (11)
- Eli Schwartz (1)
- Filipe Laíns (4)
- JCWasmx86 (1)
- Michael Forney (2)
- Michal Sieron (2)
- Seedo Paul (27)
- Sertonix (3)
- Stone Tickle (497)
- Thomas Adam (1)
- Tokunori Ikegami (1)
- Vincent Torri (12)
- kzc (2)
- rofl0r (1)
- sewn (1)
- torque (1)
✓ 0.2.0
released on 2023.04.07
✓ 0.1.0
released on 2022.10.11