mkbuild -- An intro

mkbuild is a make-based build system we've used for many embedded projects over the years. The goal of this build system is to provide efficient management of common code targeting multiple deeply embedded platforms. This happens quite frequently in our embedded development business. For example, a customer might go through several implementations of a hardware platform as the design is researched, evaluated, and refined.

mkbuild achieves its objectives by providing a framework where a common set of component interfaces can have multiple implementations, with selection of implementations based on information ultimately associated with the hardware platform for which the code is being compiled. The methodology is rather straight-forward:

  • Code is organized into components. Each component is a directory which also contains an import.mk file, which provide the necessary build instructions for that component.
  • mkbuild crafts a list of include directories from which to search for components, based in part upon the hardware platform targeted for compilation.
  • The application Makefile, and other components, may state a dependency on a particular component by naming that component via a make include directive. Multiple implementations may exist for the same component interface, placed within different directories, allowing for a unified application build for different platforms.

Here's an example. Say that each platform has its own implementation of a component called stdio, which provides a simple byte-based IO interface to the outside world. Each platform's implementation would be present in a platform-specific directory. mkbuild, during build, would include the proper directory for the platform for which the code is being compiled. Since each component implements the same interface, higher level code using the stdio component will be platform independent.

Code directories

mkbuild enforces a directory structure onto the code it compiles. Each application is placed in its own repository, and may contain both the application's main source code units in addition to any application specific component implementations. Other code used by an application will generally be shared code, and is therefore stored in a shared respository. mkbuild supports one, or more, shared code paths, where each path points to a checkout of the associated shared repository. Generally we've used a single library for simplicity. Below is an example of two repositories checked out on a developer workstation: an application someapp and a shared library shared.

workspace
    |
    +-- someapp
    |      |
    |      +-- Makefile
    |      +-- someapp.c
    |      +-- componentA
    |              |
    |              +-- import.mk
    |              +-- componentA.c
    |              +-- componentA.h
    +-- shared
           |
           +-- mkbuild
           |       |
           |       +-- (mkbuild files)
           |
           +-- components
           |       |
           |       +-- kinetis
           |       |       |
           |       |       +-- componentB (kinetis uC specific component)
           |       |
           |       +-- nxp_lpc
           |       |       |
           |       |       +-- componentB (lpc uC specific component)
           |       |
           |       +-- nvcfg
           |       +-- sio
           |       +-- verinfo
           |
           +-- platforms
                   |
                   +-- platformA
                   |       |
                   |       +-- mkincludes.mk
                   |       +-- rules.mk
                   |       +-- ucinit
                   |       |     |
                   |       |     +-- ucinit.c
                   |       |
                   |       +-- componentC (platform specific)
                   |
                   +-- platformB

mkbuild enforces a general structure to shared libraries, and provides an application Makefile template. Combined, mkbuild is able to construct a directory search path used during compilation for source code selection.

Navigating platform code

One challenge posted by the strategy employed by mkbuild is code management. If, for example, there exist a half-dozen implementations of a given component, which one is used for compilation of a given platform? The developer can examine the include directory order used during the build, finding the first occurrence of the component. But a simpler solution is to have mkbuild provide some cross reference information which can then be used by a code editor.

mkbuild builds two cross reference databases as part of a successful build operation: a ctags tags file, and a cscope database. Since the build products are placed in a platform-specific build directory, cross references are appropriately platform specific. Code inspection tools and editors can use these reference sources for effective code navigation.