Compiling your Application

To compile your application, an example makefile has been included. In this chapter, we will explore the features this makefile offers and help you configure it to your environment’s needs. The makefile that was used to compile the AlarmSystem project can be found here.

The nice thing about this makefile is that it can be re-used for other C projects that combine native code with code generated from Dezyne. Additionally, the makefile has gradually grown to facilitate version control. Dzn model files are treated as source code and checked into version control. Dezyne-generated C is treated similar to C++-generated object files: it is not checked into version control because it can always be regenerated.

Breakdown of example makefile: the core

Let’s break the provided makefile down to the core so we can discuss what is actually needed to compile the AlarmSystem application. The makefile assumes you will compile the application on a Raspberry Pi. To accomplish this, you need to transfer the source files and generated files to the Pi filesystem. With all that in place, the following snippet will compile the integrated application on the Pi:

CXX = g++
CXXFLAGS = -std=c++11 -I$(RUNTIME) -I$(SRC) -lwiringPi -lrt
CPPFLAGS = -MMD -MF $(@:%.o=%.d) -MT '$(@:%.o=%.d) $@' -I$(SRC) -I$(RUNTIME)
LDFLAGS = -lpthread
TARGET = dznpi
SRC = ./src
RUNTIME = ./lib
SRCS = $(wildcard $(SRC)/*.cc)
SRCS += $(wildcard $(RUNTIME)/*.cc)
OBJS = $(subst .cc,.o,$(SRCS))
       make $(TARGET)
       $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) $(LDFLAGS)
-include $(wildcard $(SRC)/*.d $(RUNTIME)/*.d)

This makefile will search for the Dezyne runtime files in the directory pointed at by the RUNTIME variable. The C++ source files of your application should be in the directory pointed at by the SRC variable.

One of the key reasons for using a makefile, aside from not having to re-type your compilation recipe every time you make a change, is to save time by dynamically deciding what files have been changed. An unchanged file often does not need to be recompiled and in larger projects you can save a lot of time by not re-compiling every single file for every small change you make. In this makefile, this dynamic checking is realised with the -MMD -MF $(@:%.o=%.d) -MT '$(@:%.o=%.d) $@' CPPFLAGS and the -include $(wildcard $(SRC)/.d $(RUNTIME)/.d) line at the end of the file.

Remember when we first inspected the generated system in Inspecting the generated system? You were told that inclusion of runtime files must be done using ‘ <> ‘ tags and that this would have some implications for compiling the application. These implications are represented in the makefile by the -$(RUNTIME) -I$(SRC) flags in CXX/CPPFLAGS. This means that for every compilation step, RUNTIME and SRC point to directories that must be searched for included dependencies. With these flags added to your compilation recipe, ‘ <> ‘ tags can be used and the generated code can find its required headers.

The –lwiringPi and –lrt flags are required to be able to make use of the WiringPi library. Due to the –lrt flag, the pthread library must be linked to the executable post compilation; this is done with the LDFLAGS = -lpthread compilation step.

With this makefile and a properly structured filesystem containing your project, you can already compile an application consisting of native and generated Dezyne code. Awesome! In case you are unsure of what your filesystem should look like, you can use the Github repository as inspiration:


With this folder structure, if you type ‘make’ in a terminal window on your Raspberry Pi while inside the same folder the makefile is in, an executable called ‘dznpi’ will be created. If you run the dznpi executable on your Pi and have connected the RGB led to the GPIO pins you defined to be the Red, Green and Blue RGB pins then you should be able to control the dznpi application with sensorTriggered and validPincode events through the command line UI!

Breakdown of example makefile: version control improvements

If you’ve used version control systems (VCS) before, you have probably encountered conventions for what files are allowed to be stored on the VCS. One strategy that often occurs is to not commit temporary files such as *.o files to your VCS. To facilitate for this, you can add some clean-up recipes to the makefile like the following:

RM = rm –f
      $(RM) $(OBJS) $(TARGET)

Typing make clean on your command line within the respective project folder will remove all of the generated *.o files as well as the compiled executable. This is a good first step in cleaning up your working directory before committing your local changes to a VCS.

Breakdown of example makefile: automating Dezyne features

This improvement requires you to be able to access the dzn client from your command line. To learn how to do this, refer to this support article or follow the instructions on how to set your PATH variable after installation of Dezyne through the Eclipse client.

In a way, generated code from Dezyne models can also be seen as temporary files, just like the *.o files from the previous paragraph. The source files for the generated code are your *.dzn files, and by including the generation of code in a makefile recipe it becomes easy to compile the application with only the makefile and source code in your VCS.

The generation of code from Dezyne models and its removal before committing can both be automated in the makefile. The generated code shares a version dependency with the runtime files, so to avoid any clashes on that aspect the example makefile also has a recipe to download the runtime.

For the generation of C++ code and the downloading of the Dezyne runtime, the following recipes were added:

VERSION = 2.3.3
CURRENT := $(shell dzn query | grep '*' | sed 's,* ,,')
ifneq ($(VERSION),$(CURRENT))
$(info current version: $(CURRENT) is not equal to selected version: $(VERSION))
runtime: | $(RUNTIME)/dzn
          for f in $(shell dzn ls -R /share/runtime/c++ | sed 's,/c++/,,' | tail -n +3); do           dzn cat --version=$(VERSION) /share/runtime/c++/$f > $(RUNTIME)/$f;           done
          touch $@
          mkdir -p $@
generate: $(wildcard *.dzn)
          for f in $^; do dzn -v code --version=$(VERSION) -l c++ -o $(SRC) --depends=.d $f; done
          touch $@

These additions check the version you’re using against the newest possible version of Dezyne so you are notified if there is a new Dezyne available. The runtime recipe checks if the required folder structure is already present; if it is not, it will be created. Then, it checks Verum’s runtime repository and downloads the respective files for the C++ runtime.

The generate recipe checks for all *.dzn files in the root directory of your project where your makefile is located and generates C++ code for all of the interfaces and components in the files. The generated code is stored in the folder specified by the SRC variable that was declared earlier in the makefile; this ensures that native and generated code exist in the same directory. Lastly, the ‐‐depends option ensures that extra *.dzn.d files are generated that provide information about what *.cc and *.hh files are generated from their respective *.dzn files and their dependencies. The information in these files can be used for a final step in the VCS improvements: cleaning up generated code.

       $(RM) `grep -h dzn $(SRC)/*.dzn.d | sed -e 's,:.*,,' -e 's,%,.,g'`
       $(RM) $(RUNTIME)
       $(RM) runtime generate

The recipe for cleaning up generated code is a bit harder to read, but in essence what it does is it reads the contents of the *.dzn.d files (that were generated with the ‐‐depends option of dzn code generation). In these *.dzn.d files, the names of generated files can be found; the make recipe clean_generated then removes all of the files it has found from the contents of the *.dzn.d files. The recipe also removes the runtime that was downloaded.

This should cover everything that is included in the example makefile. Depending on your preferences, you can choose to include or leave out some of its features; maybe you don’t care about having generated files in your VCS for example. As long as you include the core features discussed in Breakdown of example makefile: the core, you will be able to compile your application consisting of generated and native code.

Enjoy this article? Don't forget to share.