Tags

, , , ,

I use fossil for revision control, and love it. It scales wonderfully from projects with a single developer to larger multi-seat efforts, as long as your project can adopt and embrace its specific operating principles and design philosophy.

Where it, and every other version control or configuration management system I’ve encountered, has some friction is in dealing with development projects that require the use of an IDE.

I just finished taming a project based on the Raisonance Ride7 IDE recommended by ST Micro for code targeting their STM32 processor family, and thought it would potentially be valuable to others if I documented what I had to struggle to learn to make it play well with version control.

I’ve encountered other IDEs and had the same struggle. I will likely write those up for the blog as well.

The IDE Problem

An IDE attempts to manage the complexity of the build process for software that outgrows a single source file. It usually provides a project manager where all the source files and settings are maintained, a coding editor with lots of useful bells and whistles, and a debugger integrated relatively seamlessly with the source code views provided by the editor.

A common side effect of using an IDE is that it is more difficult to also provide a stand-alone build process that does not require interactive use of the IDE to compile your code.

Another is that the IDE rarely has a well documented place where it “keeps its brain”. Meaning that the collection of metadata that describes the project itself is usually spread out in a mysterious collection of files, not all of which are plain text, and often including information (such as a cross-reference database of symbols and where they are defined and used) that is wholly derived from the project description and its source code.

When keeping a project under source code control, it is important to achieve several goals:

  1. Complete source kit. Version control should track and store every source file needed to replicate any past version, including all of the compiler, linker, and other toolchain settings needed to specify how it was built.
  2. Instance independent. Cloning and opening a working folder should produce a working build environment. Naturally, the exact compiler, IDE and other utilities would need to be installed on a compatible (enough) operating system.
  3. Path independent. A single developer should be able (disk space permitting, of course) to have more than working folder and build the project in any working folder. This is important to reduce friction when dealing with both maintenance of a shipped version and development of new features at the same time. Not every IDE makes this easy, even without revision control.

Raisonance Ride7 IDE for STM32

This is a typical IDE, recommended by STMicro as their preferred platform for building the code samples and vendor supplied library code for their STM32 ARM processors. (Ride7 also supports other STMicro product families, but I’m personally only using STM32.)

Atypically for a vendor-supplied IDE, it is not a repackaged version of Eclipse or Netbeans. It appears to be an independent development of Raisonance, who also designed the JTAG/SWD debugger hardware and many of the official reference designs.

After taming, my currently preferred Ride7 project structure has a directory tree something like this:

+---.fossil-settings        Fossil repo configuration
+---build                   Build products (not checked in)            
|   \---somelib               ditto for a library
+---doc                     Project docs outside Ride7
+---Ride                    Ride7 Project Files
+---src                     Source files
|   \---somelib               ditto for a library
\---test                    PC-based unit tests outside Ride7

In the Ride folder, I let Ride7 create its project files. The project and all sub-project metadata is collected in files with names matching the following patterns:

Pattern      keep?    Description
---------    ------   -------------
Ride/*.rprj  Checkin  Ride7 Project File, XML
Ride/*.rapp  Checkin  Ride7 build product description XML
Ride/*.xml   Ignore
Ride/*.ctx   Ignore
Ride/*.dbi   Ignore
Ride/*.xdb   Ignore   

Just a few of these files are necessary to be preserved in the fossil repository. The rest are excluded by a line in .fossil_settings/ignore-glob that reads Ride/*.xdb,Ride/*.xml,Ride/*.dbi,Ride/*.ctx.

Fortunately, the .rprj and .rapp seem to change only rarely, and in my experience only when I did modify a setting, add a new module, or otherwise clearly modify the project itself. The other files contain transient information like the list of files currently open in the IDE and the positions of all open windows, and in many cases are modified by merely opening the project in the IDE.

All of the source files are kept in the src folder (or a subfolder) and the Ride7 project is configured to put the objects and other build projects in the Build tree.

Makefile tricks for fossil and Ride7

Unfortunately, Ride7 uses an internally developed dependency calculation and project build process, and at least as of the version I have installed, does not provide a means to export the project in any useful form. It also does not provide a command line option to load and compile a project, which would be handy in a build farm as part of a production setting.

For one ongoing project where providing a known clean build from a known fossil checkin became an issue, I manually translated a transcript of a complete build in to a .BAT file that could be invoked to guarantee a clean build. I intended to automate that translation, but never got around to it. Perhaps after taming this project, I will take that step and write about it at some future time.

I placed a Makefile in the top level directory, and gave it some targets to make dealing with Ride7 a little easier.

make prepare

This target is a placeholder for whatever steps are needed to make the project ready to compile and debug in a freshly opened workspace from a clone of the repository. The most important thing it does is make sure the Build folder is present. If there were other dependencies such as an external library to download, this would be a good place to hang additional actions to verify those items.

It is expected that you need to say make prepare only once after checking out the repository.

make ride

This target launches Ride7 on the main project file. Certainly you could launch Ride7 manually from a desktop shortcut or somewhere in the Start menu, but as a longtime command line user, it is friendly to get it launched on the right working copy with less chance of confusion.

If the prepare target were guaranteed to be side-effect free when all its work is up to date, then it could be a dependency of the ride target.

make ship

This target is the root of a tree of dependencies that package up a ZIP file containing release notes and the finished .HEX file ready to be archived, delivered to production, and flashed in the target hardware. It creates a temporary folder named ship that is excluded from fossil. It operates by deleting and recreating the folder every time so that the Makefile has complete control over what goes into a shipment.

The deliverable .HEX file is a dependency. If BUILDALL.BAT is available, it is used to get a clean compilation, with a C macro defined naming the exact UUID of the checkin, decorated by “-WITH-UNCOMMITTED-CHANGES” if fossil changes can see anything not checked in. The .HEX file is renamed when it is copied into the shipment, its new name contains the project name, part of its human-readable version number, and the checkin UUID.

As a final step, make ship uses cd ship & zip -9 ../$(PROJECT)-$(VERSION).zip * to create a .zip file ready for release.

Defining VERSION and MANIFESTUUID

Given that the project is stored in a fossil repository, there is a file src/version.h which defines a few known macros, and that I only expect to be able to build for release from an open fossil workspace, I used features of GNU make and sed to define some useful Makefile macros related to the version number and fossil checkout.

First, we define $(MANIFESTUUID) to be the first 10 digits of the checkin ID.

MANIFESTUUID := $(shell sed -e "s/\(.\{10\}\).*/\1/" manifest.uuid)

Then we extract the values from three expected #define lines in src/version.h to get the major, minor, and patch level fields of the version number.

VERSIONMAJ := $(shell sed -n -e "/define VERSION_MAJ/ s/^[^0-9]*\([0-9]*\)[^0-9]*$$/\1/p" src/version.h)
VERSIONMIN := $(shell sed -n -e "/define VERSION_MIN/ s/^[^0-9]*\([0-9]*\)[^0-9]*$$/\1/p" src/version.h)
VERSIONPAT := $(shell sed -n -e "/define VERSION_PAT/ s/^[^0-9]*\([0-9]*\)[^0-9]*$$/\1/p" src/version.h)

Then we see if there are any obvious changes to the local workspace that are not yet committed to the repository. We could go further and require that the repository had also been successfully synced with a remote, or that there aren’t any changes that fossil changes cannot detect, such as new files not yet added. The latter case can be handled by also running fossil extra, but that requires that the ignore-glob be kept up to date, and that any other scrap files are regularly cleaned.

CHECKCLEAN := $(if $(shell fossil changes),-WITH-UNCOMMITTED-CHANGES)

Finally, we combine the version number pieces along with $(CHECKCLEAN) to get a string that can be used as part of a file name in any “reasonable” file system. It will look something like v1.123p42 where exacly three digits of the minor version number are displayed, with leading zeros supplied as needed.

VERSION := v$(VERSIONMAJ).$(shell echo 000$(VERSIONMIN) | sed -n -e s/^.*\([0-9]\{3\}\).*$$/\1/p )p$(VERSIONPAT)$(CHECKCLEAN)

If this were in a more frequently used Makefile, or it was necessary to be able to build for release from a source tarball (or zipball) outside of an open fossil workspace, then I would consider turning on the fossil manifest setting and working with the manifest and manifest.uuid files that creates instead. Of course, implementing the CHECKCLEAN macro “correctly” is left as an exercise.

Summary

I use fossil for version control, and find it is always worth the effort required to do so. IDEs often make that harder to do than needed. I’ve described how I tamed Raisonance Ride7’s project files in a fossil repository.

There are plenty of other IDEs, each with their own quirks. In future posts, I’ll document what I know about the Arduino IDE, Eclipse, and Visual Studio at a minimum.


If you have a project involving embedded systems, micro-controllers, electronics design, audio, video, or more we can help. Check out our main site and call or email us with your needs. No project is too small!

+1 626 303-1602
Cheshire Engineering Corp.
120 W Olive Ave
Monrovia, CA 91016


(Written with StackEdit.)

Advertisements