1
0
Fork
You've already forked genshl
0
Single Header Library generator
  • C 98.4%
  • Makefile 1.6%
2026年01月19日 21:52:33 +01:00
tests implement genshl and create test files 2026年01月17日 23:20:16 +01:00
.editorconfig implement genshl and create test files 2026年01月17日 23:20:16 +01:00
.gitignore fix doc and strict build in C ansi (c89) 2026年01月17日 23:46:05 +01:00
genshl.c fix usage help and update doc 2026年01月19日 21:52:33 +01:00
LICENSE add license 2026年01月17日 14:51:25 +01:00
Makefile change command line options 2026年01月18日 22:22:04 +01:00
README.md fix usage help and update doc 2026年01月19日 21:52:33 +01:00

Single Header Library (SHL) generator from source files

Single Header Libraries

For those not used to single header libraries, it's a kind of library (finished set of functionalities) composed of a single header file.

The library can be composed of declarations only, or have its implementation inserted directly in the header file, but activated only when a certain macro is defined (usally *LIBRARYNAME*_IMPLEMENTATION).

So you can include it in several units of your project, but with one and only one compilation unit including it with this macro defined.

my_shl.c implements the library in only one compilation unit

#define MY_SHL_IMPLEMENTATION
#include "my_shl.h"

other.c is a compilation unit using the library

#include "my_shl.h"
/* define functions using stuff declared in my_shl.h */

main.c also uses the library

#include "my_shl.h"
int main(void) {
 /* use stuff declared in my_shl.h */
 return 0;
}

Structure of a single header library

/* classic header file part */
#ifndef MY_SHL_H
#define MY_SHL_H

/* declare the API of your library */
#endif /* !MY_SHL_H */
/* here starts the implementation part */
#ifdef MY_SHL_IMPLEMENTATION
/* not mandatory but a good practice to avoid double compilation if the same
 * header file is reincluded from elsewhere in the compilation unit including it
 */
#undef MY_SHL_IMPLEMENTATION

/* define implementation of declarations made in header part */
#endif /* MY_SHL_IMPLEMENTATION */

Interest of a generator

Although SHL have a real deployment advantage, having everything in the same header file can be tricky for development because the file can be quite huge and some syntax highligther might be lost understanding it (especially those based on a language server).

To keep the ease of separate files for each part of the library (one header file and several implementation source files), a generator can be used. That's exactly what genshl does.

project directory

  • src
    • my_shl.h (this will be the header part)
    • my_shl_internal.h
    • my_shl_init.c
    • my_shl_func1.c
    • my_shl_func2.c
  • tools
    • genshl.c
  • my_shl.h (this is generated)

You develop as usual your library in several compilation units and usually one header file. When you want to release/git push your project, you use genshl to generate the single header library file (here at the root of the project).

This can also be used to provide two ways to use your library: the classic way with static/dynamic linking and as a single header library.

Using genshl

It's a command line tool accepting a few arguments and printing to standard output (or in a file with the -o option) the generated file.

It can run in two different modes, the simple one where you enumerate input files (headers and sources) and the template mode where you specify a template file that contains references to input files.

For projects made from the ground up to be a single header library, the simpler former mode is preferred, whereas for projects that were not initiality thought for that kind of target, the template mode is certainly the best way.

Simple mode

genshl [-o {output_file.h}] [-d {implementation_define} [-u]]
 [-i {include_segments_to_remove}] [-b {begin_string}] [-f {finish_string}]
 [-h {origin_header_file.h}] [-h {other_header_file.h}]... [source_file.c ...]

Template mode

genshl [-o {outout_file.h}] [-t {template_file}] [-g {global_directory}]
 [-i {include_segments_to_remove}] [-b {begin_string}] [-f {finish_string}]

As soon as a template file is detected, other input related options are ignored (-d, -u, -h, -p, -e and any source file).

-o {output_file.h}

Tells genshl to print the result in a file (in the example above my_shl.h at the project root); without it, result is printed to standard output.

-t {template_file}

Path to an existing file to take as a template for the output. This file contains references to header and source files and those reference lines will be replaced by actual files content in the output, using the same processing than the simple mode (removing ignored blocks and project related includes).

See below for a description of the template format.

-g {global_dir}

When a template file is used, file paths referenced in it don't have to be absolute. If they are relative, they are relative to the directory specified in this option.

-i {include_dir}

For the process of removing inner project's includes, the directory part of the #include instruction is skipped during check of files referenced for the header part (either added with -h or with %$...$% in the template file).

This option permits to prefix the file name with a project related directory (see example below).

-b {begin_string} and -f {finish_string}

Used together, they define strings that trigger beginning and end of ignored lines (see example below).

-h {origin_header_file.h}

Is a file to be in the header part; if none is specified, that will be an implementation only library (sic). You can work with one or several on your project and group them in the output.

-d {implementation_define}

Instructs genshl to insert the implementation macro (MY_SHL_IMPLEMENTATION on the example above); without it, you need a prologe source file defining it and an epilogue one for the #endif (see examples below).

-u

Also set the #undef just after the #define when -d is used.

source_file.c

Is a file to be in the implementation part; You can specify several files that will be concatenated, with includes to headers and sources of the project (aka in the list of headers or sources on the command line) removed. If none is specified, the interest of genshl is kinda superflous :)

One header, one to several sources

The classic example is to generate a SHL from one header file and one to several sources, eventually with one to several internal headers (only for implementation). The example above is typical and will be create that way:

genshl -o my_shl.h -h src/my_shl.h -d MY_SHL_IMPLEMENTATION -u \
 src/my_shl_internal.h src/*.c

Own separation with implementation

If you want to control how the implementation macro is defined/used, you can omit the -d switch (as well as -u as it won't be used anyway) and add surrounding internal headers to the sources list:

src/begin-impl.h

/*****************************************************************************/
/* IMPLEMENTATION */
/*****************************************************************************/
#ifdef MY_SHL_IMPLEMENTATION
#undef MY_SHL_IMPLEMENTATION

src/end-impl.h


#endif /* MY_SHL_IMPLEMENTATION */
genshl -o my_shl.h -h src/my_shl.h \
 src/begin-impl.h src/my_shl_internal.h src/*.c src/end-impl.h

Note that the order of files in the list is important, especially in this case.

Remove parts of headers/sources

You might want to remove some heading comments from source files in the result. For this specify start and end tags and place those in your files. Every line between those tags (including lines having tags themselves) will be ignored:

src/source1.c

/* {{NO_SHL */
/* src/source1.c - implement function1()
 *
 * This whole comment will be ignored
 */
/* }}NO_SHL */
genshl -o my_shl.h -h src/my_shl.h -d MY_SHL_IMPLEMENTATION -u \
 -b "{{NO_SHL" -f "}}NO_SHL" src/my_shl_internal.h src/*.c

Using -i {include_dir}

Let's say the original project structure is as follow:

  • include
    • my_shl
      • intro.h
      • my_shl.h
  • src
    • internal.h
    • func1.c
    • func2.c

You expect your source files to include my_shl/any_header.h or even my_shl.h to include my_shl/intro.h, like this:

internal.h

#include <my_shl/my_shl.h>/* anything ... */

For genshl to be able to find those references, the simpliest way is to check only for file names without a path prefix (aka #include <my_shl.h> or #include "my_shl.h").

Setting the option -i my_shl will force genshl to check includes in the form #include <my_shl/my_shl.h> or #include "my_shl/my_shl.h".

Using -t {template_file}

It is a file of any kind, containing reference that will be replaced by those files content. Every reference enclosed between $ will be treated as headers and those between % as sources. Of source, in this mode, you handle the implementation macro yourself.

References must be on their own line (with an ending newline)

/* Heading my_shl, document here *:
$src/my_shl.h$
#ifdef MY_SHL_IMPLEMENTATION
#undef MY_SHL_IMPLEMENTATION
%src/internal.h%
%src/func1.c%
%src/func2.c%
#endif /* MY_SHL_IMPLEMENTATION */

Of course this simple example has no real interest my itself, but imagine you work on a project that has not been made to be a single header library and has platform specific sources. It would be a mess to deal with prefix/suffix headers like those: src/plat1-prefix.h

#ifdef __plat1__

src/plat1-suffix.h

#endif /* __plat1__ */
genshl -h src/my_shl.h -d MY_SHL_IMPLEMENTATION \
 src/internal.h src/func1.c src/func2.c \
 src/plat1-prefix.h src/plat1.c src/plat1-suffix.h \
 src/plat2-prefix.h src/plat2.c src/plat2-suffix.h
 ...

While it's easy with a template:

$src/my_shl.h$
#ifdef MY_SHL_IMPLEMENTATION
#undef MY_SHL_IMPLEMENTATION

%src/internal.h%
%src/func1.c%
%src/func2.c%
#ifdef __plat1__
%src/plat1.c%
#endif /* __plat1__ */#ifdef __plat2__
%src/plat2.c%
#endif /* __plat2__ */
#endif /* MY_SHL_IMPLEMENTATION */
genshl -t my_shl.template.h -g src

Building genshl

I've created genshl as simple and standard as possible to be easily included in any project, aka having the source genshl.c and creating the binary on the fly in your project workflow is as simple as cc -o genshl genshl.c (or any compiler with any compiler options).

It is compliant with old C89 standard (-ansi flag for gcc) and has been tested with highest warning level (-Wall -Wextra -pedantic).

License

Like STB, genshl is in the public domain (unlicense.org). You can do anything you want with it. You have no legal obligation to do anything else, although I appreciate attribution.

It is also licensed under the MIT open source license, if you have lawyers who are unhappy with public domain.

Author note: I have even copied this licensing block from STB :)