Make hacks: embedded version info, detecting non timestamp change and automatically generated dependencies

For the purposes of traceability you may wish to embed the version of a program into it. If you’re using full on CI, you very much shouldn’t need this. If your CI system doesn’t record such things, then you need to fix it now.

But if you’re hacking around locally, especially for research it can be really useful to know where that executable came from or more specifically where some results came from, because it’s easy to lose track of that. An easy way to do this is to embed the git hash of the repository into the executable so it can be written alongside the data.

Essentially if git status --porcelain prints nothing then the repository is entirely clean. With that in mind, here’s a short script which generates a C++ source file with the git hash if the repository is clean, and prints a loud, red warning if it is not clean. Here is get_version.sh:

git_hash=`git rev-parse HEAD`

if [[ "$(git status --porcelain)" != "" ]]
then
	echo -e "\033[31mThere are uncommitted changes. This means that the build" 1>&2
	echo -e "Will not represent a traceable version.\033[0m" 1>&2
	time=`date +%s`
	version="${git_hash}-${time}"
else
	version="${git_hash}"
fi

cat <<FOO
namespace version{
	const char* version_string = "$version";
}
FOO

It’s easy enough to use from make:

.PHONY: FORCE
versioninfo.cc: FORCE
	bash get_version.sh > versioninfo.cc

and of course make your program depend on versioninfo.o. But it’s not very clean; this will rebuild and then re-link every single time. The key is to make the FORCE dependency depend on whether anything has changed.

This script (get_version_target.sh), reruns get_version.sh and compares it to the existing versioninfo.cc. If there’s a change, it prints the target (FORCE), otherwise it prints nothing.

if ! diff -q versioninfo.cc <( bash get_version.sh 2> /dev/null ) > /dev/null 2>&1
then
        echo FORCE
fi

You then need to plumb this into make using the shell command:

version_target=$(shell bash get_version_target.sh)
.PHONY: FORCE 
versioninfo.cc: $(version_target)
	bash get_version.sh > versioninfo.cc

This will now only re-generate versioninfo.cc (and hence the .o and the final executables) if the git hash changes.

With the basics in place, you can make the version info as detailed as you like, for example you could record any tags and branch names, so you could record those and if it’s an actual point release etc. The downside of the shell commands is that they run every time make is called so you will want to make them fast otherwise incremental rebuilds will become annoyingly slow.

Using this mechanism, make can be much more malleable than expected. This is an immensely powerful feature. But remember:

Today’s hack

Let’s say you’re automating a git workflow for a variety of good and bad reasons. Commits are fine, you can just do a:

commit -am 'a message'

and it goes through non interactively. Now let’s say you have a merge and you try:

git merge

It pop open an editor window to let you type a message. There’s no merge -m option to let you provide a message. If you look closely, you’ll see that the editor window already has a message filled in saying it’s a merge. This means if you save without exiting, git sees a message and decides all is OK and it can proceed. So all you have to do is provide an “editor” which quits without saving every time. The program which does nothing except exit successfully is /true so you can simply do:

EDITOR=true git merge

for a non interactive merge.

I don’t know whether to be proud or ashamed, but it works.

 

Small hack of the day

Two things:

  1. syntax highlighting is nice
  2. wouldn’t it be nice to copy /paste it

The main problem is many terminals don’t for no especially good reason allow copy/paste of formatted text. There are three tools which help:

  1. xclip -t text/html this eats standard input and allows pasting of it as HTML, so it can include formatting and color and so on. By default, xclip does plain text, so you have to specify that it’s HTML.
  2. aha this takes ANSI formatted text (i.e. the formatting scheme used by terminals and turns it into HTML).
  3.  unbuffer many tools will only write color output to a terminal, not a pipe. Through the magic of PTYs (pseudoterminals) this program fools other programs into thinking they’re running on a terminal when they’re not.

Now all you have to do is pipe them together. So, take a silly C++ program like this:

void silly;

And compile it like this:

unbuffer g++-7 error.cc -std=c++1z  | aha | xclip -t text/html

Then a simple paste into a box which accepts HTML (e.g. email, etc) gives:

error.cc:2:6: error: variable or field 'silly' declared void
 void silly;
      ^~~~~

 

Overwrite a file but only if it exists (in BASH)

Imagine you have a line in a script:

cat /some/image.img > /dev/mmcblk0

which dumps a disk image on to an SD card. I have such a line as part of the setup. Actually, a more realistic one is:

pv /some/image.img | sudo 'bash -c cat >/dev/mmcblk0'

which displays a progress bar and doesn’t require having the whole script being run as root. Either way, there’s a problem: if the SD card doesn’t happen to be in when that line runs, it will create /dev/mmcblk0. Then all subsequent writes will go really fast (at the speed of the main disk), and you will get confused and sad when none of the changes are reflected on the SD card. You might even reboot which will magically fix the problem (/dev gets nuked). That happened to me 😦

The weird, out of place dd tool offers a nice fix:

pv /some/image.img | sudo dd conv=nocreat of=/dev/mmcblk0

You can specify a pseudo-conversion, which tells it to not create the file if it doesn’t already exist. It also serves the second purpose as the “sudo tee” idiom but without dumping everything to stdout.

A simple hack but a useful one. You can do similar things like append, but only if it exists, too. The incantation is:

dd conv=nocreat,notrunc oflag=append of=/file/to/append/to

That is, don’t create the file, don’t truncate it on opening and append. If you allow it to truncate, then it will truncate then append, which is entirely equivalent to overwriting but nonetheless you can still specify append without notrunc.

Warn or exit on failure in a shell script

Make has a handy feature where when a rule fails, it will stop whatever it’s doing. Often though you simply want a linear list of commands to be run in sequence (i.e. a shell script) with the same feature.

You can more or less hack that feature with BASH using the DEBUG trap. The trap executes a hook before every command is run, so you can use it to test the result of the previous command. That of course leaves the last command dangling, so you can put the same hook on the EXIT trap which runs after the last command finishes.

Here’s the snippet and example which warns (rather than exits) on failure:

function test_failure(){
  #Save the exit code, since anything else will trash it.
  v=$?
  if [ $v != 0 ]
  then
    echo -e Line $LINE command "\e[31m$COM\e[0m" failed with code $v
  fi
  #This trap runs before a command, so we use it to
  #test the previous command run. So, save the details for
  #next time.
  COM=${BASH_COMMAND}
  LINE=${BASH_LINENO}
}

#Set up the traps
trap test_failure EXIT
trap test_failure DEBUG

#Some misc stuff to test.
echo hello
sleep 2 ; bash -c 'exit 3'

echo world
false

echo what > /this/is/not/writable

echo the end

Running it produces:

$ bash errors.bash 
hello
Line 21 command bash -c 'exit 3' failed with code 3
world
Line 25 command false failed with code 1
errors.bash: line 27: /this/is/not/writable: No such file or directory
Line 27 command echo what > /this/is/not/writable failed with code 1
the end
$