How to Incorporate Linker Scripts in Meson Cross-Compilation Files

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:

  1. The path(s) to the linker scripts used in our build, relative to the root of the project directory.
    1. 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.
  2. The names of the script(s) to invoke in the build using the -T flag
  3. 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 of linker_paths and linker_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 through linker_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:

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.