1.0 Introduction
This article provides a brief overview of how to write new C-code code that extends or enhances the core Fossil binary.
New features can be added to a Fossil server using external CGI programs, but that is not what this article is about. This article focuses on how to make changes to Fossil itself.
2.0 Programming Language
Fossil is written in C-89. There are specific style guidelines that are required for any new code that will be accepted into the Fossil core. But, of course, if you are writing an extension just for yourself, you can use any programming style you want.
The source code for Fossil is not sent directly into the C compiler. There are three separate code preprocessors that run over the code first.
- The mkindex preprocessor scans all regular source files looking for special comments that contain "help" text and which identify routines that implement specific commands or which generate particular web pages.
- The makeheaders preprocessor generates all the ".h" files automatically. Fossil programmers write ".c" files only and let the makeheaders preprocessor create the ".h" files.
- The translate preprocessor converts source code lines that begin with "@" into string literals, or into print statements that generate web page output, depending on context.
The Makefile for Fossil takes care of running these preprocessors with all the right arguments and in the right order. So it is not necessary to understand the details of how these preprocessors work. (Though, the sources for all three preprocessors are included in the source tree and are well commented, if you want to dig deeper.) It is only necessary to know that these preprocessors exist and hence will affect the way you write code.
3.0 Adding New Source Code Files
New source code files are added in the "src/" subdirectory of the Fossil source tree. Suppose one wants to add a new source code file named "xyzzy.c". The first step is to add this file to the various makefiles. Do so by editing the file tools/makemake.tcl and adding "xyzzy" (without the final ".c") to the list of source modules at the top of that script. Save the result and then run the makemake.tcl script using a TCL interpreter. The command to run the makemake.tcl script is:
tclsh makemake.tcl
The working directory must be src/ when the command above is run. Note that TCL is not normally required to build Fossil, but it is required for this step. If you do not have a TCL interpreter on your system already, they are easy to install. A popular choice is the Active Tcl installation from ActiveState.
After the makefiles have been updated, create the xyzzy.c source file from the following template:
/* ** Copyright boilerplate goes here. ***************************************************** ** High-level description of what this module goes ** here. */ #include "config.h" #include "xyzzy.h" #if INTERFACE /* Exported object (structure) definitions or #defines ** go here */ #endif /* INTERFACE */ /* New code goes here */
Note in particular the #include "xyzzy.h" line near the top. The "xyzzy.h" file is automatically generated by makeheaders. Every normal Fossil source file must have a #include at the top that imports its private header file. (Some source files, such as "sqlite3.c" are exceptions to this rule. Don't worry about those exceptions. The files you write will require this #include line.)
The "#if INTERFACE ... #endif" section is optional and is only needed if there are structure definitions or typedefs or macros that need to be used by other source code files. The makeheaders preprocessor uses definitions in the INTERFACE section to help it generate header files. See makeheaders.html for additional information.
After creating a template file such as shown above, and after updating the makefiles, you should be able to recompile Fossil and have it include your new source file, even before your source file contains any code. It is recommended that you try this.
Be sure to fossil add your new source file to the self-hosting Fossil repository and then commit your changes!
4.0 Creating A New Command
By "commands" we mean the keywords that follow "fossil" when invoking Fossil from the command-line. So, for example, in
fossil diff xyzzy.c
The "command" is "diff". Commands may optionally be followed by arguments and/or options. To create new commands in Fossil, add code (either to an existing source file, or to a new source file created as described above) according to the following template:
/* ** COMMAND: xyzzy ** ** Help text goes here. Backslashes must be escaped. */ void xyzzy_cmd(void){ /* Implement the command here */ fossil_print("Hello, World!\n"); }
The example above creates a new command named "xyzzy" that prints the message "Hello, World!" on the console. This command is a normal command that will show up in the list of command from fossil help. If you add an asterisk to the end of the command name, like this:
** COMMAND: xyzzy*
Then the command will only show up if you add the "--all" option to fossil help. Or, if the command name starts with "test" then the command will be considered experimental and will only show up when the --test option is used with fossil help.
The example above is a fully functioning Fossil command. You can add the text shown to an existing Fossil source file, recompiling then test it out by typing:
./fossil xyzzy ./fossil help xyzzy ./fossil xyzzy --help
The name of the C function that implements the command can be anything you like (as long as it does not collide with some other symbol in the Fossil code) but it is traditional to name the function "commandname_cmd", as is done in the example.
You could also use "printf()" instead of "fossil_print()" to generate the output text, if desired. But "fossil_print()" is recommended as it has extra logic to insert \r characters at the right times on Windows systems.
Once you have the command running, you can then start adding code to make it do useful things. There are lots of utility functions in Fossil for parsing command-line options and for opening and accessing and manipulating the repository and the working check-out. Study implementations of existing commands to get an idea of how things are done. You can easily find the implementations of existing commands by searching for "COMMAND: name" in the files of the "src/" directory.
5.0 Creating A New Web Page
As with commands, new webpages can be added simply by inserting a function that generates the webpage together with a special header comment. A template follows:
/* ** WEBPAGE: helloworld */ void helloworld_page(void){ style_header("Hello World!"); @ <p>Hello, World!</p> style_footer(); }
Add the code above to a new or existing Fossil source code file, then recompile fossil and run fossil ui then enter "http://localhost:8080/helloworld" in your web browser and the routine above will generate a web page that says "Hello World." It really is that simple.
The special "WEBPAGE:" comment is picked up by the "mkindex" preprocessor and used to generate a table that maps the "helloworld" webpage name into a pointer to the "helloworld_page()" function. The function that implements a webpage can be named anything you like (as long as it does not collide with another name) but the traditional name is "pagename_page".
HTML pages begin with a call to style_header() and end with the call to style_footer(). Content is generated by the "@" lines that are translated (by the "translate" preprocessor) into printf-like code that generates the content of the webpage. Different techniques are used to generate non-HTML content. In the unlikely event that you need to generate non-HTML content, look at existing webpage implementations (ex: "logo" or "style.css") to see how that is done.
There are lots of other things that a real web-page implementation will need to do, such as verifying user credentials, parsing query parameters, and interacting with the repository. But now that you have the general idea of how webpages are implemented, you can look at the many other webpage implementations already built into Fossil to see how all that works.