Using Custom Build Steps with Eclipse Auto-generated Makefiles

Today we have a guest post by Paul Shepherd. Paul describes the process he uses to define custom build steps when using Eclipse auto-generated Makefiles. He uses build version tagging to demonstrate this process.

Paul is the Lead Electrical Engineer at Benchmark Space Systems. He has spent time in the Aerospace, Consumer Audio, and Integrated Circuits fields, more often than not working at the intersection of hardware and software. You can contact him via LinkedIn.

Using Custom Build Steps with Eclipse Auto-generated Makefiles

Benchmark Space Systems recently had the opportunity to work with Embedded Artistry to develop our internal Software Release and Continuous Integration processes. From our first meeting, it was clear we shared a common belief that a good process is one where quality is the default outcome, rather than something you have to fight for. Furthermore, best practices aren’t dependent on your tools, but sometimes your tools guide how you implement those best practices. (Side note: Embedded Artistry’s Technology Radar is a good survey of the space that spans best practices and tools.)

In this short series, I’ll share how we implemented our CI process steps. In today’s post, we will cover implementing build version tagging with the Eclipse IDE and auto-generated Makefiles. In the next post, I’ll share how we got unit testing reports up and running on our production build server. 

Benchmark Space Systems’ electronics utilize the MSP430FR5969-SP or its standard-reliability sibling. We use TI’s Code Composer Studio (CCS) for most of our development. CCS is built on the Eclipse IDE platform. We use auto-generated Makefiles to build our project locally.

We create our production builds on an Ubuntu build server running Jenkins. Performing the steps to define a build version is straightforward on the build server, since we maintain a custom Makefile under source control for producing our production builds. However, implementing the versioning steps with the Eclipse IDE posed a problem: if we are letting Eclipse automatically generate your Makefile, how can we add custom steps to the build process?

Inserting Custom Build Steps

Fortunately, Eclipse provides mechanisms for implementing custom build steps: the makefile.init, makefile.defs, and makefile.targets files. The auto-generated Makefile always includes these files from the project root directory. These files are optional, and the build process will work if they do not exist.

Makefile.init is included at the beginning and allows for custom initialization steps. Makefile.defs is included before the first compiler invocation and allows symbols to be defined. Finally, makefile.targets is included at the end of the generated makefile, and allows for defining custom build targets.

For build-tagging, we define the variable GEN_OPTS__FLAG in makefile.defs, and the compiler invocation includes this variable. This variable isn’t limited to just defining new symbols, so it has to include the complete argument, starting with –define as you can see below.

Appending new symbol definitions to the  GEN_OPTS__FLAG  inside  makefile.defs.

Appending new symbol definitions to the GEN_OPTS__FLAG inside makefile.defs.

Since Eclipse automatically includes this file in the generated Makefile, these symbols will be used by the compile steps.

GEN_OPTS__FLAG  is included in the automatically generated compile step in the  subdir_rules.mk  file.

GEN_OPTS__FLAG is included in the automatically generated compile step in the subdir_rules.mk file.

Handling Different Operating Systems

The exact formatting of the commands or expressions used within the Makefile depends on the operating system and shell you are using. On Windows, Eclipse will use cmd.exe to run all of the build steps, while Ubuntu uses Bash.

For example, we use the shell to generate the build timestamp and build machine strings. On Windows, the OS won’t easily give us a tidy build date and time, so we execute a two statement Python script:

BUILD_DATE := $(shell python -c "from datetime import datetime; print(datetime.utcnow().strftime('%d/%m/%Y, %H:%M'))"

BUILD_MACHINE := $(shell echo %username%)@$(shell hostname)

While Linux is much simpler, and only needs:

BUILD_DATE := $(shell date -u +"%d/%m/%Y, %H:%M")
BUILD_MACHINE := $(shell whoami)@$(shell hostname)

In order to support multiple platforms, we use the $(SHELL) variable in our makefile.defs file to select the proper behavior.

ifeq ($(SHELL), cmd.exe)
BUILD_DATE := $(shell python -c "from datetime import datetime; print(datetime.utcnow().strftime('%d/%m/%Y, %H:%M'))"
BUILD_MACHINE := $(shell echo %username%)@$(shell hostname)
else
BUILD_MACHINE := $(shell whoami)@$(shell hostname)
BUILD_DATE := $(shell date -u +"%d/%m/%Y, %H:%M")
endif

Putting it All Together

Here is our full Makefile.defs file:

ifeq ($(SHELL), cmd.exe)
BUILD_DATE := $(shell python -c "from datetime import datetime; print(datetime.utcnow().strftime('%d/%m/%Y, %H:%M'))"
BUILD_MACHINE := $(shell echo %username%)@$(shell hostname)
else
BUILD_MACHINE := $(shell whoami)@$(shell hostname)
BUILD_DATE := $(shell date -u +"%d/%m/%Y, %H:%M")
endif 

version := $(subst -, ,$(shell git describe --long --dirty --tags))
COMMIT := $(strip $(word 3, $(version)))
COMMITS_PAST := $(strip $(word 2, $(version)))
DIRTY := $(strip $(word 4, $(version)))

ifneq ($(COMMITS_PAST), 0)
         BUILD_INFO_COMMITS := .$(COMMITS_PAST)
endif

ifneq ($(DIRTY),)
         BUILD_INFO_DIRTY :=+
endif

VERSION_TAG :=$(strip $(word 1, $(version)))
BUILD_INFO := $(COMMIT)$(BUILD_INFO_COMMITS)$(BUILD_INFO_DIRTY)

$(info Build Time: $(BUILD_DATE))
$(info Build Version: $(VERSION_TAG))
$(info Build Info: $(BUILD_INFO))

GEN_OPTS__FLAG += --define=VERSION_TAG="\"$(VERSION_TAG)\"" \
               --define=VERSION_BUILD_INFO="\"$(BUILD_INFO)\""\
               --define=VERSION_BUILD_MACHINE="\"$(BUILD_MACHINE)\""\
               --define=VERSION_BUILD_DATE="\"$(BUILD_DATE)\""

The makefile.init, makefile.defs, and makefile.targets files offer a middle ground between managing the complete build process on your own and giving up all control to an auto-generated build. You can use this example as a template for implementing your own custom build steps in projects using the Eclipse IDE.

Coming Up Next

In our next blog post, we will share our approach for setting up the Ceedling+Unity unit testing framework to output reports and posting them on a Jenkins/Pipeline build server.

Further Reading

Related Articles

Related Books