C++ Development Setup - No Time for Sword Fights

1. Overview

I don't like waiting for compilations to finish, or for unit tests to run. In the ideal case, output from compiling and running tests should be available whenever I decide to look at it. Focus shouldn't have to leave the code editor.

After a series of iterations, I've created a setup that is not in the way. A setup that automatically rebuilds when code is changed, and executes tests that have been affected. A setup where I can use global hotkeys to issue a full rebuild, launch the main program, or run all tests.

This is what the end result looks like.

Click to play

Note that focus never leaves the editor!

I'll go through each of the necessary pieces of the puzzle, and put it together along the way.

2. Automatic Rebuild

This section describes a script that issues a rebuild whenever a source file changes.

Click to play

If you understand the following snippet

Line 1-6:Finds all relevant projects files.
Line 7: Finds the 'last modified' timestamp for all files.
Line 8: Reduces the above to a single md5 hash.
... you might want to skip ahead to the final script.

2.1 Find all relevant project files

Using the find-utility, one can list all files with particular extension with
$ find . -name "*.cpp"

To find both .cpp and .h files:
$ find . -name "*.cpp" -or -name "*.h"

To avoid finding hidden files:
$ find . -name "[!\.]*.cpp" -or -name "[!\.]*.h"

To do the same but limit results to ./src directory:
$ find ./src -name "[!\.]*.cpp" -or -name "[!\.]*.h"

Putting it together: Find all non-hidden files in ./src directory with .cpp .h .tpp extensions, any SConscript* files, and also the SConstruct file in root directory:

$ find ./src ./SConstruct \
 -name "[!\.]*.cpp" -or \
 -name "[!\.]*.h"   -or \
 -name "[!\.]*.tpp" -or \
 -name "SConscript*" -or \
 -name "SConstruct"

2.2 Determine 'last modified'-timestamp

Unix command stat shows detailed timestamp information of a file.

$ stat SConstruct
  File: `SConstruct'
  Size: 778       	Blocks: 24         IO Block: 4096   regular file
Device: 14h/20d	Inode: 1208177     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/   okami)   Gid: ( 1000/   okami)
Access: 2013-06-21 09:08:13.198346996 +0200
Modify: 2013-06-21 09:08:13.030346998 +0200
Change: 2013-06-21 09:08:13.030346998 +0200
 Birth: -

To only list modification time:
$ stat SConstruct -c %y
2013-06-21 09:08:13.030346998 +0200

Putting it together: Determine the last modified timestamp of all files found by find

$ find ./src ./SConstruct \
 -name "[!\.]*.cpp" -or \
 -name "[!\.]*.h"   -or \
 -name "[!\.]*.tpp" -or \
 -name "SConscript*" -or \
 -name "SConstruct" | xargs stat -c %y

2.3 Use md5sum to simplify timestamp information

md5 is a hashing algorithm that reduces any chunk of information to a 128-bit value. To compute the md5 hash, use md5sum.

Computing the md5 hash of myfile.txt:
$ md5sum myfile.txt
09f7e02f1290be211da707a266f153b3 myfile.txt

Or pipe the content to md5:
$ cat myfile.txt | md5sum
09f7e02f1290be211da707a266f153b3 -

Putting it together: By piping all the timestamps above, we reduce the information to a single value. If any timestamp changes, the value changes.

$ find ./src ./SConstruct \
 -name "[!\.]*.cpp" -or \
 -name "[!\.]*.h"   -or \
 -name "[!\.]*.tpp" -or \
 -name "SConscript*" -or \
 -name "SConstruct" | xargs stat -c %y | md5sum

2.4 Automatic Rebuild Script

3. Automatic Test Execution

I compile each test suite into its own executable, and put each executable in a particular directory. Automatic test-execution becomes as simple as monitoring a directory, and executing tests if they change. This is what it looks like:

Click to play

This python-script does just that. It monitors executables in a directory, and executes any that change.

4. Redirecting Ouput to Specific TTY

Taking advantage of terminals, and redirecting output to a specific TTY is central to the setup. It allows us to set up global hotkeys that run scripts that send the output to a particular terminal. The TTY demystified is a good article on TTYs if you want to read more about it, but from a practical point of view, this is all you need to know:

  • Each of your terminal sessions has a TTY-number.
  • You can determine this number with the tty command.
  • You can send output to a particular TTY by redirecting it to /dev/pts/TTY.

Click to play

Global Hotkeys

Setting up global hotkeys is a distro-specific issue, so it won't be covered. The global hotkey should trigger a script that determines the correct project path and redirects output to a particular tty.

I use three scripts for the following:

recompile:Does a full build from scratch
run-main:Runs the executable created by the compilation.
run-tests:Runs all the tests.

I suggest writing additional scripts to help the 'compile', 'run-main' and 'run-tests' script:

marktty:Writes your current TTY to a file, e.g. ~/scripts/ttynumber.
markttytest:Writes your current TTY to a file, e.g ~/scripts/ttynumber_test.
markproj:Writes the `pwd` to a file, e.g. ~/scripts/project

5. Extra - tmuxinator

Creating two terminal windows, starting automatic building, starting automatic test execution, registering each tty to receive global triggered scripts... it takes time. Roughly half a minute.

It would be nice to have a way to automate this as well. There are several tools that can help you. One such is tmuxinator. Here is the setup script I use.

Line  2:cdpval is a script that outputs the project directory.
Line  9: Top pane for autobuild, set to execute ./bin/main.
Uses marktty to receive global build and run.
Line 10: Bottom pane for autodirexec, set to monitor test directory.
Uses markttytest to receive global test run.

In action:

Click to play

6. Conclusion

This concludes my first article. If you would like clarification on any particular aspect, let me know. I'm happy to get any tips, or suggestions as to how to improve it, both the article and setup.

Using this setup has been very useful to me, and hopefully this might give some inspiration to set up something similar, or devise something better. Maybe you already have, and would like to share.