Login
HackersGuide
Login

Hacker's Guide

See also: building, AmalgamationBuild

This page primarily contains notes for anyone who's actively hacking on libfossil internals, e.g. changing fsl_cx's structure.

An exceedingly brief intro to the source tree:

Getting it Running Locally (on Unix-like systems)

Because of where the libs live vs where the binaries live, the easiest ways to get the source tree running its test apps are...

The following text assumes the {CHECKOUT} is the top-most directory of this source tree's checkout and that the tree has been built already using:

$ ./configure
$ make

(FWIW, i use ./configure CC="ccache cc"; make -j4, noting that "make" must be GNU Make, which is called gmake on some platforms.)

Now, to get the binaries working...

First, and simplest, is to add {CHECKOUT}/src to one's $LD_LIBRARY_PATH and {CHECKOUT}/f-apps to the $PATH. libfossil.* lives, as of this writing, in the src dir, but building from the top-most directory will also symlink those there. The build tree is not set up to do out-of-tree builds (and won't be unless someone who cares about that patches it to do so).

Slightly more elaborate is...

  1. From a directory in the $LD_LIBRARY PATH, type:
    ln -s {CHECKOUT}/src/libfossil.* .
    For my systems, that's ~/lib.

  2. Symlink all of the f-apps to a directory in the $PATH. For example:

$ cd ~/bin
$ for i in {CHECKOUT}/f-apps/f-*; do
  test -x "$i" && ln -s "$i" . # filter out .o/.c files
done

Or:

$ cd ~/bin
$ ln -s {CHECKOUT}/f-apps/f-* .
$ rm f-*.o f-*.c

As new f-apps are added, add a symlink to the appropriate directory.

An Introduction to structs...

All (or almost all) structs in the library are accompanied by other constructs which help ensure consistency regarding how library-level structs are to be initialized. The general pattern looks like this:

struct fsl_foo { ... };
typedef struct fsl_foo fsl_foo;
extern const fsl_foo_empty;
#define fsl_foo_empty_m { ...member initializers... }

The purpose of the _empty_m macro is to define a cleanly-initialized const state for that struct. The fsl_foo_empty instance is guaranteed to be initialized using fsl_foo_empty_m, but both the empty and empty_m variants are provided because code requires different initializers in different contexts (examples are provided below).

The _m suffix denotes "macro", by the way.

All non-opaque structs can and should be initialized in code like this:

fsl_buffer buf = fsl_buffer_empty;
fsl_deck deck = fsl_deck_empty;

// The "empty_m" macros are required mainly for in-struct initialization:
struct {
  fsl_buffer buf;
  fsl_deck deck;
} foo = {
  fsl_buffer_empty_m,
  fsl_deck_empty_m
};

That ensures that all struct members get set to known default values (which need not be NULL/0), and helps harden the code against uninitialized memory access (since all members get initialized to whatever the struct developer deemed sensible, and NULL is generally the sensible default for pointer members).

When updating structs...

When changing the members of structs, it is critical that the accompanying empty_m macro (described above) also be updated. Failing to do so can lead to undefined results (best case is a compilation failure when the shared struct empty instance is initialized from the empty_m macro).

After changing a struct, make sure to do a full/clean rebuild or you might see really weird results in code compiled against older struct.

Generating Fancy Call Dependency Graphics

The build tree has a configuration option to compile with gprof profiling information if gcc is the compiler. It can be used like:

$ ./configure --profile CC="ccache gcc"
$ make

That changes the build process in two significant ways. First, it adds the -pg compilation and linking flag to all of the C files. Secondly, it tells the f-apps to statically link against both libfossil and sqlite3 so that gprof can generate the information it needs (noting that the build process explicitly filters the -pg flag out of the sqlite3 build because those bits are just noise to us for these purposes).

One easy way to generate graphs from that is to use gprof2dot. In a nutshell:

$ sudo apt install python3 graphviz
$ pip install gprof2dot
#   Then run a libfossil binary, e.g.:
$ ./f-parseparty --dry-run
#   That generates a file called gmon.out, which can then be processed
#   by running gprof from that same dir, passing it the name of the binary
#   (without any arguments), and:
$ gprof ./f-parseparty | gprof2dot -z main | dot -Tpng -o outfile.png

Without the -z main flag, the output may contain many functions along the top of the graphic from sqlite (its library-level initialization?) which are not relevant.

Similar results can be achieved with callgrind, albeit with slightly noisier output, using:

$ alias cg='valgrind --tool=callgrind --callgrind-out-file="callgrind.out"'
$ cg ./f-app
$ gprof2dot -f callgrind < callgrind.out  | dot -Tpng -o f-app.png

The KDE-based app KCacheGrind can read those callgrind outputs as well and provides a nice interactive interface for analyzing and navigating them:

$ alias kcg
alias kcg='kcachegrind callgrind.out &>/dev/null &'
#   From the dir where callgrind.out was generated:
$ kcg

Attachments: