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:
- The primary "exported" distributable is the AmalgamationBuild. The other sources and headers are essentially components for creating that deliverable.
- Public headers go in
include/fossil-scm/
. There is at least one pseudo-private header (fossil-internal.h
) there as well, but that one is under consideration for being moved to... - Private impl files and headers go in src/.
- Demo/test apps can be found in f-apps/.
- The truly adventurous might want to explore the other various subdirs, like sql/ (schema files - these get compiled into the library as C code), bindings/cpp/ (one of any number of potential C++ bindings) and bindings/s2/ (one of any number of potential script bindings).
- The "native" build platform requires GNU Make 3.81 or higher and the conventional set of Unix-side build tools. Contributions of control files (or READMEs) for other build platforms are of course welcomed.
- The overall style is a direct artifact (as it were) of fossil(1), and we ask that all contributors maintain the general overall style. We are not picky about placement of spaces around parenthesis and whatnot, mainly just the overall 2-space indentation and
{
bracing}
styles, as well as the naming style for public symbols (e.g. a prefix offsl_
on most symbols). The one notable exception is how APIs are documented: libfossil uses Doxygen style and Doxygen cannot swallow fossil's native comment style.
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...
From a directory in the
$LD_LIBRARY PATH
, type:
ln -s {CHECKOUT}/src/libfossil.* .
For my systems, that's~/lib
.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:
- fossil-logo-3.png [download] added by stephan on 2021-07-16 18:13:35. [details]