Showing posts with label cmake. Show all posts
Showing posts with label cmake. Show all posts

Jun 9, 2010

Automate version numbering using Git

Anar here...

Since the time I moved from SVN to Git, I was looking for a convenient and automated way to build version numbers for my projects. Same as always, it appears to be very easy with Git ;)

So, coming to the subject. Every time I make a stable release, I create a tag object in git with "git tag -a vX.Y".
Now using "git describe --abbrev=4 HEAD" I can get a canonical version number, something like this: vX.Y-NN-SSSS, where: vX.Y is the latest tag, NN is a number of commits since the tag and SSSS is object's hash or just vX.Y if I would check out an exactly released tag.

The possible workflow could be the following:
  1. Check that project's git repo. is accessible. This is a normal case for developers builds (see #2). If it is a user who runs the build from projects's source tarball, than the repo will be inaccessible (see #3).
  2. If the repo is accessible, use "git describe" to get an annotated version number. Process some needed customizations of the version string (for example, change '-' to '.' and remove leading 'v'). Write the version string to a version file, which will be distributed in source tarballs.
  3. If the repo is inaccessible, just read the version string from the version file or use a default one.
Simple is that...

It is much easer now to maintain versioning. Every stable release, which is done after a tagging, will be named as a tag name. For example, if you have tagged v2.2, than this algorithms will return a "2.2" as a version of the project. But all nightly releases or patch releases will get additional info, like "2.2-110-gede7". Using this information you can also easily find out what exactly were changed by asking git log or git describe and giving them a version string as a parameter.

There is no need now to have 3 sections in a version string when tagging: Major.Minor.Patch. Since all patch releases will get a patch (commit) counter automatically, like: "2.2.110.ssss" and accordingly a stable release will be just "2.2".

Well I use cmake as a build system for my projects, it was therefore easer for me to implement the algorithm in the CMakeLists.txt - a build rules file. One can do the same using an external shell script or something like that.
This is what I wrote on short notice:
if( EXISTS "${CMAKE_SOURCE_DIR}/.git" )

execute_process(COMMAND git describe --abbrev=4 HEAD
COMMAND sed -e "s/-/./g"
OUTPUT_VARIABLE POD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
# remove leading "v"
string(REGEX REPLACE "^v(.*)" "\\1" POD_VERSION ${POD_VERSION})
execute_process( COMMAND bash -c "echo \"${POD_VERSION}\" > ${CMAKE_SOURCE_DIR}/etc/version" )

else( EXISTS "${CMAKE_SOURCE_DIR}/.git" )

execute_process(COMMAND cat ${CMAKE_SOURCE_DIR}/etc/version
OUTPUT_VARIABLE POD_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)

endif( EXISTS "${CMAKE_SOURCE_DIR}/.git" )
This implementation returns X.Y for stable releases and X.Y.Z.SS for patch releases and nightly builds.

UPDATE: By the way, I've just came across a GIT-VERSION-GEN script, which is used in the Git distributions. It does exactly this task, but a bit differently. It's very much recommend to check it out.

Mar 3, 2010

malloc/free interception on Mac OS X

There is a very easy way to hook malloc/free calls on Linux and on other systems, which support the GNU C library. Simply because the GNU C library lets you modify the behavior of malloc, realloc, and free by specifying appropriate hook functions.

Now, how about hooking malloc/free calls on Mac OS X?

Actually it appears to be also not a difficult task in some terms.
The very simple way of changing the behavior of malloc, calloc, realloc, and free routines is to implement your own zone (you have to be very careful with this one) or one can override calls of the default zone, which is automatically created for each process.
Let's try the second one.

We assume, for example, that we want to track all allocations and deallocations in a given application on Mac OS X.
The first step to achieve our goal consists of asking for our default zone structure. The following statement is doing that:
malloc_zone_t *zone = malloc_default_zone();
Don't forget to check for errors, and check that you got your zone.
The next step is to save default zone's callbacks to some tmp storage. You can either save this zone to a static malloc_zone_t variable (save the entire structure):
...
static malloc_zone_t original_zone;
...
original_zone = *zone;

or you can save each callback, you need to override, to a tmp variables:

system_malloc = zone->malloc;
system_free = zone->free;

the same you can do for calloc and realloc and company...

The final step is to override default hooks with custom ones:

zone->malloc = &my_malloc;
zone->free = &my_free;

which could look like the following:

void *(*system_malloc)(malloc_zone_t *zone, size_t size);
...
void (*system_free)(malloc_zone_t *zone, void *ptr);
...
void my_free(malloc_zone_t *zone, void *ptr)
{
malloc_printf("free(zone=%p, ptr=%p)\n", zone, ptr);
system_free(zone, ptr); // or if you save the whole zone: (*original_zone.free)(zone, ptr);
}
and so on...

In my_free, my_malloc and my_whatever, we can call some of our custom code to track allocations/deallocations and (if needed) we call the default function accordingly to let default zone do its job.

Snow Leopard
There is on thing you should know if you want to run your malloc hook on the Snow Leopard. According to Apple's C library malloc.c, implementing free hook is not enough to handle all frees.
Check the implementation of the free function from the library:

void
free(void *ptr) {
malloc_zone_t *zone;
size_t size;
if (!ptr)
return;
zone = find_registered_zone(ptr, &size);
if (!zone) {
malloc_printf("*** error for object %p: pointer being freed was not allocated\n"
"*** set a breakpoint in malloc_error_break to debug\n", ptr);
malloc_error_break();
if ((malloc_debug_flags & (SCALABLE_MALLOC_ABORT_ON_CORRUPTION|SCALABLE_MALLOC_ABORT_ON_ERROR)))
abort();
} else if (zone->version >= 6 && zone->free_definite_size)
malloc_zone_free_definite_size(zone, ptr, size);
else
malloc_zone_free(zone, ptr);
}

As you can see, one should also override/hook a newly added call for malloc_zone_free_definite_size:

system_free_definite_size = zone->free_definite_size;
zone->free_definite_size = my_free_definite_size;

I would also use a condition before using free_definite_size. I prefer cmake, I therefore have something like that in my CMakeLists.txt:


#
# Check that malloc_zone_t has the free_definite_size memeber
#
if(APPLE)
include(CheckCSourceCompiles)
check_c_source_compiles("
#include
void main () {
malloc_zone_t zone;
zone.free_definite_size = NULL;
}
" APPLE_MALLOC_ZONE_FREE_DEFINITE_SIZE)
endif(APPLE)

and in the source code:

#if defined(APPLE_MALLOC_ZONE_FREE_DEFINITE_SIZE)
if (zone->version >= 6 && zone->free_definite_size)
{
system_free_definite_size = zone->free_definite_size;
zone->free_definite_size = my_free_definite_size;
}
#endif

That's It.
This technique works on Mac OS X 10.5-10.6. Enjoy!