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.
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.
This section describes a script that issues a rebuild whenever a source file changes.
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. |
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"
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
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
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:
This python-script does just that. It monitors executables in a directory, and executes any that change.
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:
tty
command./dev/pts/TTY
.
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 |
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:
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.