The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
12 KiB
306 lines
12 KiB
--- |
|
short-description: Setting up cross-compilation |
|
... |
|
|
|
# Cross compilation |
|
|
|
Meson has full support for cross compilation. Since cross compiling is |
|
more complicated than native building, let's first go over some |
|
nomenclature. The three most important definitions are traditionally |
|
called *build*, *host* and *target*. This is confusing because those |
|
terms are used for quite many different things. To simplify the issue, |
|
we are going to call these the *build machine*, *host machine* and |
|
*target machine*. Their definitions are the following: |
|
|
|
* *build machine* is the computer that is doing the actual compiling. |
|
* *host machine* is the machine on which the compiled binary will run. |
|
* *target machine* is the machine on which the compiled binary's |
|
output will run, *only meaningful* if the program produces |
|
machine-specific output. |
|
|
|
The `tl/dr` summary is the following: if you are doing regular cross |
|
compilation, you only care about `build_machine` and |
|
`host_machine`. Just ignore `target_machine` altogether and you will |
|
be correct 99% of the time. Only compilers and similar tools care |
|
about the target machine. In fact, for so-called "multi-target" tools |
|
the target machine need not be fixed at build-time like the others but |
|
chosen at runtime, so `target_machine` *still* doesn't matter. If your |
|
needs are more complex or you are interested in the actual details, do |
|
read on. |
|
|
|
This might be easier to understand through examples. Let's start with |
|
the regular, not cross-compiling case. In these cases all of these |
|
three machines are the same. Simple so far. |
|
|
|
Let's next look at the most common cross-compilation setup. Let's |
|
suppose you are on a 64 bit OSX machine and you are cross compiling a |
|
binary that will run on a 32 bit ARM Linux board. In this case your |
|
*build machine* is 64 bit OSX and both your *host* and *target |
|
machines* are 32 bit ARM Linux. This should be quite understandable as |
|
well. |
|
|
|
It gets a bit trickier when we think about how the cross compiler was |
|
generated. It was built and it runs on a specific platform but the |
|
output it generates is for a different platform. In this case *build* |
|
and *host machines* are the same, but *target machine* is different. |
|
|
|
The most complicated case is when you cross-compile a cross |
|
compiler. As an example you can, on a Linux machine, generate a cross |
|
compiler that runs on Windows but produces binaries on MIPS Linux. In |
|
this case *build machine* is x86 Linux, *host machine* is x86 Windows |
|
and *target machine* is MIPS Linux. This setup is known as the |
|
[Canadian |
|
Cross](https://en.wikipedia.org/wiki/Cross_compiler#Canadian_Cross). As |
|
a side note, be careful when reading cross compilation articles on |
|
Wikipedia or the net in general. It is very common for them to get |
|
build, host and target mixed up, even in consecutive sentences, which |
|
can leave you puzzled until you figure it out. |
|
|
|
A lot of confusion stems from the fact that when you cross-compile |
|
something, the 3 systems (*build*, *host*, and *target*) used when |
|
building the cross compiler don't align with the ones used when |
|
building something with that newly-built cross compiler. To take our |
|
Canadian Cross scenario from above (for full generality), since its |
|
*host machine* is x86 Windows, the *build machine* of anything we |
|
build with it is *x86 Windows*. And since its *target machine* is MIPS |
|
Linux, the *host machine* of anything we build with it is *MIPS |
|
Linux*. Only the *target machine* of whatever we build with it can be |
|
freely chosen by us, say if we want to build another cross compiler |
|
that runs on MIPS Linux and targets Aarch64 iOS. As this example |
|
hopefully makes clear to you, the platforms are shifted over to the |
|
left by one position. |
|
|
|
If you did not understand all of the details, don't worry. For most |
|
people it takes a while to wrap their head around these |
|
concepts. Don't panic, it might take a while to click, but you will |
|
get the hang of it eventually. |
|
|
|
## Defining the environment |
|
|
|
Meson requires you to write a cross build definition file. It defines |
|
various properties of the cross build environment. The cross file |
|
consists of different sections. The first one is the list of |
|
executables that we are going to use. A sample snippet might look like |
|
this: |
|
|
|
```ini |
|
[binaries] |
|
c = '/usr/bin/i586-mingw32msvc-gcc' |
|
cpp = '/usr/bin/i586-mingw32msvc-g++' |
|
ar = '/usr/i586-mingw32msvc/bin/ar' |
|
strip = '/usr/i586-mingw32msvc/bin/strip' |
|
pkgconfig = '/usr/bin/i586-mingw32msvc-pkg-config' |
|
exe_wrapper = 'wine' # A command used to run generated executables. |
|
``` |
|
|
|
The entries are pretty self explanatory but the last line is |
|
special. It defines a *wrapper command* that can be used to run |
|
executables for this host. In this case we can use Wine, which runs |
|
Windows applications on Linux. Other choices include running the |
|
application with qemu or a hardware simulator. If you have this kind |
|
of a wrapper, these lines are all you need to write. Meson will |
|
automatically use the given wrapper when it needs to run host |
|
binaries. This happens e.g. when running the project's test suite. |
|
|
|
The next section lists properties of the cross compiler and its target |
|
system, and thus properties of host system of what we're building. It |
|
looks like this: |
|
|
|
```ini |
|
[properties] |
|
sizeof_int = 4 |
|
sizeof_wchar_t = 4 |
|
sizeof_void* = 4 |
|
|
|
alignment_char = 1 |
|
alignment_void* = 4 |
|
alignment_double = 4 |
|
|
|
has_function_printf = true |
|
|
|
c_args = ['-DCROSS=1', '-DSOMETHING=3'] |
|
c_link_args = ['-some_link_arg'] |
|
``` |
|
|
|
In most cases you don't need the size and alignment settings, Meson |
|
will detect all these by compiling and running some sample |
|
programs. If your build requires some piece of data that is not listed |
|
here, Meson will stop and write an error message describing how to fix |
|
the issue. If you need extra compiler arguments to be used during |
|
cross compilation you can set them with `[langname]_args = |
|
[args]`. Just remember to specify the args as an array and not as a |
|
single string (i.e. not as `'-DCROSS=1 -DSOMETHING=3'`). |
|
|
|
One important thing to note, if you did not define an `exe_wrapper` in |
|
the previous section, is that Meson will make a best-effort guess at |
|
whether it can run the generated binaries on the build machine. It |
|
determines whether this is possible by looking at the `system` and |
|
`cpu_family` of build vs host. There will however be cases where they |
|
do match up, but the build machine is actually not compatible with the |
|
host machine. Typically this will happen if the libc used by the build |
|
and host machines are incompatible, or the code relies on kernel |
|
features not available on the build machine. One concrete example is a |
|
macOS build machine producing binaries for an iOS Simulator x86-64 |
|
host. They're both `darwin` and the same architecture, but their |
|
binaries are not actually compatible. In such cases you may use the |
|
`needs_exe_wrapper` property to override the auto-detection: |
|
|
|
```ini |
|
[properties] |
|
needs_exe_wrapper = true |
|
``` |
|
|
|
The last bit is the definition of host and target machines. Every |
|
cross build definition must have one or both of them. If it had |
|
neither, the build would not be a cross build but a native build. You |
|
do not need to define the build machine, as all necessary information |
|
about it is extracted automatically. The definitions for host and |
|
target machines look the same. Here is a sample for host machine. |
|
|
|
```ini |
|
[host_machine] |
|
system = 'windows' |
|
cpu_family = 'x86' |
|
cpu = 'i686' |
|
endian = 'little' |
|
``` |
|
|
|
These values define the machines sufficiently for cross compilation |
|
purposes. The corresponding target definition would look the same but |
|
have `target_machine` in the header. These values are available in |
|
your Meson scripts. There are three predefined variables called, |
|
surprisingly, `build_machine`, `host_machine` and |
|
`target_machine`. Determining the operating system of your host |
|
machine is simply a matter of calling `host_machine.system()`. |
|
|
|
There are two different values for the CPU. The first one is |
|
`cpu_family`. It is a general type of the CPU. Common values might |
|
include `x86`, `arm` or `x86_64`. The second value is `cpu` which is a |
|
more specific subtype for the CPU. Typical values for a `x86` CPU |
|
family might include `i386` or `i586` and for `arm` family `armv5` or |
|
`armv7hl`. Note that CPU type strings are very system dependent. You |
|
might get a different value if you check its value on the same machine |
|
but with different operating systems. |
|
|
|
If you do not define your host machine, it is assumed to be the build |
|
machine. Similarly if you do not specify target machine, it is assumed |
|
to be the host machine. |
|
|
|
## Starting a cross build |
|
|
|
|
|
Once you have the cross file, starting a build is simple |
|
|
|
```console |
|
$ meson srcdir builddir --cross-file cross_file.txt |
|
``` |
|
|
|
Once configuration is done, compilation is started by invoking Ninja |
|
in the usual way. |
|
|
|
## Introspection and system checks |
|
|
|
The main *meson* object provides two functions to determine cross |
|
compilation status. |
|
|
|
```meson |
|
meson.is_cross_build() # returns true when cross compiling |
|
meson.has_exe_wrapper() # returns true if an exe wrapper has been defined |
|
``` |
|
|
|
Note that the latter gives undefined return value when doing a native |
|
build. |
|
|
|
You can run system checks on both the system compiler or the cross |
|
compiler. You just have to specify which one to use. |
|
|
|
```meson |
|
build_compiler = meson.get_compiler('c', native : true) |
|
host_compiler = meson.get_compiler('c', native : false) |
|
|
|
build_int_size = build_compiler.sizeof('int') |
|
host_int_size = host_compiler.sizeof('int') |
|
``` |
|
|
|
## Mixing host and build targets |
|
|
|
Sometimes you need to build a tool which is used to generate source |
|
files. These are then compiled for the actual target. For this you |
|
would want to build some targets with the system's native |
|
compiler. This requires only one extra keyword argument. |
|
|
|
```meson |
|
native_exe = executable('mygen', 'mygen.c', native : true) |
|
``` |
|
|
|
You can then take `native_exe` and use it as part of a generator rule or anything else you might want. |
|
|
|
## Using a custom standard library |
|
|
|
Sometimes in cross compilation you need to build your own standard |
|
library instead of using the one provided by the compiler. Meson has |
|
built-in support for switching standard libraries transparently. The |
|
invocation to use in your cross file is the following: |
|
|
|
```ini |
|
[properties] |
|
c_stdlib = ['mylibc', 'mylibc_dep'] # Subproject name, dependency name |
|
``` |
|
|
|
This specifies that C standard library is provided in the Meson |
|
subproject `mylibc` in internal dependency variable `mylibc_dep`. It |
|
is used on every cross built C target in the entire source tree |
|
(including subprojects) and the standard library is disabled. The |
|
build definitions of these targets do not need any modification. |
|
|
|
## Changing cross file settings |
|
|
|
Cross file settings are only read when the build directory is set up |
|
the first time. Any changes to them after the fact will be |
|
ignored. This is the same as regular compiles where you can't change |
|
the compiler once a build tree has been set up. If you need to edit |
|
your cross file, then you need to wipe your build tree and recreate it |
|
from scratch. |
|
|
|
## Custom data |
|
|
|
You can store arbitrary data in `properties` and access them from your |
|
Meson files. As an example if you cross file has this: |
|
|
|
```ini |
|
[properties] |
|
somekey = 'somevalue' |
|
``` |
|
|
|
then you can access that using the `meson` object like this: |
|
|
|
```meson |
|
myvar = meson.get_cross_property('somekey') |
|
# myvar now has the value 'somevalue' |
|
``` |
|
|
|
## Cross file locations |
|
|
|
As of version 0.44.0 meson supports loading cross files from system locations |
|
(except on Windows). This will be $XDG_DATA_DIRS/meson/cross, or if |
|
XDG_DATA_DIRS is undefined, then /usr/local/share/meson/cross and |
|
/usr/share/meson/cross will be tried in that order, for system wide cross |
|
files. User local files can be put in $XDG_DATA_HOME/meson/cross, or |
|
~/.local/share/meson/cross if that is undefined. |
|
|
|
The order of locations tried is as follows: |
|
- A file relative to the local dir |
|
- The user local location |
|
- The system wide locations in order |
|
|
|
Distributions are encouraged to ship cross files either with |
|
their cross compiler toolchain packages or as a standalone package, and put |
|
them in one of the system paths referenced above. |
|
|
|
These files can be loaded automatically without adding a path to the cross |
|
file. For example, if a ~/.local/share/meson/cross contains a file called x86-linux, |
|
then the following command would start a cross build using that cross files: |
|
|
|
```sh |
|
meson builddir/ --cross-file x86-linux |
|
```
|
|
|