Meson has a concept of machine files which can be used to define a cross-compilation environment or a build machine environment. The idea is that the primary meson.build
files describe how the project is built, while the machine files can supply the specific toolchain binaries and compilation flags necessary for a particular environment.
Following this logic, I wanted to move my linker script arguments into my cross files. My thought was that I could move my linker scripts to a standard location relative to the project root, and use that path with the -L
flag.
c_link_args = [ '-mcpu=cortex-m3', '-mfloat-abi=soft', '-mabi=aapcs', '-mthumb', '-TSTM32F103VBIx_FLASH.ld', '-Lbuild/linker/stm']
When I attempted to run this, my link step failed because the target for my application is defined in a subfolder (src/app
), so the path to the linker script is incorrect (The argument should be -L ../../build/linker/stm
). I could hard-code that particular path, but the resulting cross-file wouldn’t work on other projects using a different directory structure.
With a little bit of ingenuity, I figured out a reusable "generic" solution using custom cross file properties.
The Solution
In order to create a truly useful linker-script setup, we need to know three pieces of information:
- The path(s) to the linker scripts used in our build, relative to the root of the project directory.
- Some linker scripts include other linker scripts using
INCLUDE
directives, and these may be located in different directories than the primary script(s). The paths would need to be included for the files to be found.
- Some linker scripts include other linker scripts using
- The names of the script(s) to invoke in the build using the
-T
flag - Linker script files to use as dependencies when linking our targets, such that if we modify the file, the executable target will be automatically relinked.
We’ll add properties to our cross files to represent each of these pieces of information. linker_scripts
can just contain the file name, as long as linker_paths
includes a path containing that script. We can provide explicit file paths in the link_depends
folder.
linker_paths = ['build/linker/stm/']
linker_scripts = ['STM32F103VBIx_FLASH.ld']
link_depends = ['build/linker/stm/STM32F103VBIx_FLASH.ld']
Note: Now, there seems to be some duplication in our properties, especially because
link_depends
is a combination oflinker_paths
andlinker_scripts
. This is true when using a single linker script, but not in the case where you are using a linker script that includes other scripts. If the included scripts are contained in different directories, the linker needs to be told throughlinker_paths
. For proper dependency mapping, you would need a dependency on the main linker script as well as the included scripts, even though you are only invoking one script with the-T
argument.
In our build, we need to process each of these properties to generate the proper set of flags, as well as the proper dependencies. Since each of these values is an array, we can operate on each entry using a foreach
loop, add the proper compiler flag, and add it to a generic variable such as linker_script_flags
. The link_depends
list is handled in a different way: we append the absolute path to the top of the source tree, and generate an array of filenames that we can pass to the link_depends
argument for an executable
target.
linker_script_flags = []
linker_script_deps = []
foreach entry : meson.get_cross_property('linker_paths', [''])
if entry != ''
linker_script_flags += '-L' + meson.source_root() / entry
endif
endforeach
foreach entry : meson.get_cross_property('linker_scripts', [''])
if entry != ''
linker_script_flags += '-T' + entry
endif
endforeach
foreach entry : meson.get_cross_property('link_depends', [''])
if entry != ''
linker_script_deps += meson.source_root() / entry
endif
endforeach
You can add these linker flags to individual targets:
sample_app = executable('sample_app',
'app/main.c',
dependencies: libc_dep,
link_args: [
## Processed linker script flags used below
linker_script_flags,
map_file.format(meson.current_build_dir()+'/sample_app'),
],
## Linker script dependencies
link_depends: linker_script_deps,
)
You could also add linker_script_flags
to every cross-compiled target in your project:
add_project_link_arguments(linker_script_flags, language: ['c', 'cpp'])
Note that if you take this approach, you would still need to specify link_depends
with each executable
target in your build.
sample_app = executable('sample_app',
'app/main.c',
dependencies: libc_dep,
link_args: [
map_file.format(meson.current_build_dir()+'/sample_app'),
],
## Linker script dependencies
link_depends: linker_script_deps,
)
Reusable Build Module
I like to convert reusable Meson constructs into build modules that I can use on all of my Meson projects. I have a [GitHub project](https://github.com/embeddedartistry/meson-buildsystem/ containing reusable modules and tooling scripts. I include this project as a submodule in all of my Meson projects in a standardized location: build/
at the root of the source tree.
To convert this code into a module, I created a standalone meson.build
file in a dedicated folder: build/linker/linker-script-as-property
. Inside of the file I pasted the contents of the script:
linker_script_flags = []
linker_script_deps = []
foreach entry : meson.get_cross_property('linker_paths', [''])
if entry != ''
linker_script_flags += '-L' + meson.source_root() / entry
endif
endforeach
foreach entry : meson.get_cross_property('linker_scripts', [''])
if entry != ''
linker_script_flags += '-T' + entry
endif
endforeach
foreach entry : meson.get_cross_property('link_depends', [''])
if entry != ''
linker_script_deps += meson.source_root() / entry
endif
endforeach
To use this module in a build, I make a subdir
call from my top-level meson.build
file:
subdir('build/linker/linker-script-as-property')
And then I can access the populated linker_script_flags
and linker_script_deps
variables.
The module is designed so that it will function even if a cross file does not contain the specified properties. At least, Meson will not abort the build if the properties are missing… but that doesn’t mean your application will link properly!
Learn More About Our Approach to Meson
Want to learn more about using Meson on complex embedded projects or building a reusable project skeleton? Check out these courses: