yotta defines the way that software builds in order to make it easier for
separate modules to work together. It has both a simple automatic build system
that will build modules based on the source files discovered in the source
directory, and the capability to support building modules of any complexity
using CMake.
yotta makes some useful information available to the modules being compiled, which can be embedded in the built binary or otherwise used at compile time. The majority of this information is defined by yotta’s configuration system, but some other information is also available.
The name of the library being built by the current module is available as
YOTTA_MODULE_NAME
, as if it were defined:
#define YOTTA_MODULE_NAME modulename-unquoted
No header needs to be included for this definition to be available.
Use the preprocessor stringification trick to get the module name as a string, if desired. Note that this definition is not currently available when compiling tests, and there are other circumstances where using custom CMake can make it unavailable.
If the yotta build information header is included, then you can also access other information. Note that this header changes with every build (as it includes a build timestamp and unique ID), so do not include it unnecessarily.
To include the yotta build information header:
#include YOTTA_BUILD_INFO_HEADER
This header defines the following:
#define YOTTA_BUILD_YEAR 2015 // the current year, UTC
#define YOTTA_BUILD_MONTH 9 // the current month, 1-12, UTC
#define YOTTA_BUILD_DAY 16 // the current day of the month, 1-31, UTC
#define YOTTA_BUILD_HOUR 18 // UTC hour 0-23
#define YOTTA_BUILD_MINUTE 16 // UTC minute 0-59
#define YOTTA_BUILD_SECOND 47 // UTC second 0-61 (1)
#define YOTTA_BUILD_UUID 1f37f267-f31b-48c0-bfdd-2a7a5449817b // a uuid representing the build (2)
If yotta finds a mercurial or git repository in the module or application being built, then the following will also be defined in this header:
#define YOTTA_BUILD_VCS_ID 0123456789abcdef // git or mercurial hash, variable length up to 40 characters
#define YOTTA_BUILD_VCS_CLEAN 1 // 1 if there were no uncommitted changes, else 0
#define YOTTA_BUILD_VCS_DESCRIPTION v0.5-57-gad36348 // git describe or mercurial equivalent
Corresponding definitions for all of the build information are always available in CMake without including any files.
Notes:
yotta will automatically build the contents of the source
and test
subdirectories of a software module. If you wan to exclude files from being
picked up by this build system then you can add them to a
.yotta_ignore
file placed at the top of the module.
Any files in the source directory, and any of its subdirectories, will be compiled into a single static library (for normal modules), or into the application (for an executable module).
Any source files at the top-level of the test directory will be compiled into
separate test executables, and the (recursive) contents of any subdirectories
will each be compiled into a single test executable. You can use the
yotta test
subcommand to build and run these tests.
Files in any other directories are normally ignored by yotta. By convention, public header files that a module exposes are placed in a subdirectory with the same name as the module, and then should be included as:
#include "modulename/headername.h"
To override yotta’s default build rules for the source
and test
directories, place your own CMakeLists.txt file in these directories. yotta
will also ensure that any CMakeLists.txt file in any other top-level
subdirectory of your module is included in the build. The testing with yotta
guide explains how to make yotta aware of any tests you add manually so that
yotta test
can run them.
yotta will also respect a CMakeLists.txt file at the top-level of your module. If this file is detected, then yotta will not automatically generate any build rules. This is useful if you’re adding yotta support to a module with an existing build system, especially if the build system already uses CMake.
To ensure that yotta can automatically link your module to other modules, make sure you define exactly one library with the same name as your module. A custom build system may also define other build artifacts. In this case take care to ensure that you name them in a way that minimizes the likelihood of name collisions with other modules.
There are various places you can define CMake rules to control the build, these each have different effects:
./CMakeLists.txt
(in the module root): if you define a
CMakeLists.txt
file in the root of your module or executable, then yotta
completely delegates building your module to this file../source/CMakeLists.txt
: defining a CMakeLists.txt
file in the
source directory replaces yotta’s default build rules for your library or
executable, but yotta will still generate default rules for yout test
directory (if any)../source/<anything>.cmake
: any .cmake files found in the source
directory will be included at the end of the yotta-generated build rules
for the source directory. If you want to make a very simple modification
(such as definining a new preprocessor macro that your module needs), then
this is the best way to do it../test/CMakeLists.txt
: defining a CMakeLists.txt
file in the
test directory replaces yotta’s default build rules for your tests. yotta
will build your library or executable from the contents of the source
directory as normal../<anything>/CMakeLists.txt
: Any subdirectory with a CMakeLists.txt
file will be included in the build (unless it is ignored in the
.yotta_ignore file). There aren’t very many good reasons to do this.All the following examples are using standard CMake syntax. For documentation on the commands used, pleas see the CMake docs.
General tips for writing CMake:
Always wrap expanded "${VARIABLES}"
in quotes (or expand them inside a
quoted string), if they are unquoted then any spaces in the expanded
variable will cause it to be split into separate arguments
Where possible, avoid overriding yotta’s generated CMakeLists.txt, and use
the automatically included .cmake
files to modify what yotta defined
instead.
If you have a script ./scripts/munge.py <input> <output>
that you want to run on an input file
./resources/input.data
to generate an output C file to include in the build,
you can use a custom CMake file like this:
./source/CMakeLists.txt
# construct the output path for our generated file (in the build directory, so
# that it gets removed by `yotta clean`):
set(MYMODULE_GENERATED_FILES ${CMAKE_BINARY_DIR}/generated/mymodule)
# ensure that the directory for the generated file exists:
file(MAKE_DIRECTORY "${MYMODULE_GENERATED_FILES}")
# save the paths to the script, input and output files, for convenience:
set(MYMODULE_MUNGE_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/../scripts/munge.py")
set(MYMODULE_MUNGE_INPUT "${CMAKE_CURRENT_LIST_DIR}/../resources/input.data")
set(MYMODULE_MUNGE_OUTPUT "${MYMODULE_GENERATED_FILES}/generated.c")
# define the command to generate this file
add_custom_command(
OUTPUT "${MYMODULE_GENERATED_FILES}/generated.c"
DEPENDS "${MYMODULE_MUNGE_SCRIPT}"
"${MYMODULE_MUNGE_INPUT}"
COMMAND python "${MYMODULE_MUNGE_SCRIPT}" "${MYMODULE_MUNGE_INPUT}" "${MYMODULE_MUNGE_OUTPUT}"
COMMENT "Munging input into generated.c"
)
# define the library for this module, using the generated file:
add_library(mymodule # your module must create a library with its own name
sourcefile1.c
sourcefile2.c
"${MYMODULE_GENERATED_FILES}/generated.c"
)
# link against the module's dependencies
target_link_libraries(mymodule
mydependency
myotherdependency
)
Note that as we’re replacing the yotta-generated CMakeLists for the source directory, you need to make sure you’re still linking against all of your module’s dependencies
You can use a .cmake
file to change the link flags of an existing target
without having to redefine the automatically generated build rules. For
example, if your module is called mymodule
, you could add this:
./source/override_flags.cmake
:
# add -funroll loops to the compile commands used for the sources in this
# module... loops deserve some fun too!
set_target_properties(mymodule COMPILE_FLAGS "-funroll-loops")
Note that here “mymodule” is the name of the static library that your module is generating (the CMake “target” that it defines – nothing to do with the yotta target). By convention all yotta modules produce a static library with the same name as the module
For documentation on the other things that you can set with set_target_properties, including preprocessor definitions, see the CMake docs.
./source/link_foo.cmake
:
# link ./precompiled/foo.a into mymodule, in addition to its yotta
# dependencies:
target_link_libraries(mymodule "${CMAKE_CURRENT_LIST_DIR}/../precompiled/foo.a")
yotta makes all the definitions which are available to the preprocessor available to CMake scripts (including, for example, the path to the build information header, and the definitions derived from the config information).
In addition, several other definitions are available, including:
YOTTA_CONFIG_MERGED_JSON_FILE
: This expands to the full path of a file
where the current yotta config information has been written. If you want to
use the config information for advanced pre-build steps (such as including
portions of it in the executable in a parseable form), then this is the file
you should read the config information from.Everything that yotta generates during the build is created within the build
subdirectory. Within this directory build products are further divided by the
name of the target being built. This makes it safe to
switch between building for different targets without cleaning.