c-autotools-template-small/README.md
Dan Sanderson 75729fc793 tweaks
2022-08-20 12:38:44 -07:00

319 lines
12 KiB
Markdown

# c-autotools-template-small
This is template for new C projects using [GNU
Autotools](https://www.gnu.org/software/automake/manual/html_node/index.html),
[Unity Test](http://www.throwtheswitch.org/unity), and
[CMock](http://www.throwtheswitch.org/cmock), with one C file per module.
I originally wrote
[dansanderson/c-autotools-template](https://github.com/dansanderson/c-autotools-template)
to support a notion of a C module with multiple source files, and soon felt
like I needed another layer of code generation to manage the `Makefile.am`.
That's fine, but it was a little unsatisfying to not take better advantage of
GNU Autotools to describe the project.
It was the multi-file modules bit that felt like going against the grain, so I
thought I'd try another template that assumes one C compilation unit (one `.c`
file) per "module." This is suitable for many projects, and it felt premature to
assume otherwise by default.
Project maintainers need Ruby 2.x installed for the Unity Test and CMock code
generators. The source distribution can be built on any POSIX-compliant system
without Ruby.
**Note:** Be sure to clone this repo (or your own project repo based on this
repo) with `git clone --recurse-submodules` so that the CMock testing library
in `third-party/` and its submodules are also instaled. If you omitted this
when cloning, run this to finish the process: `git submodule update --init
--recursive`
## Plain C Modules
A _module_ is defined as a C header file (`.h`) that declares the public
interface, and a C source file (`.c`) that implements it. These files live in
`src/`, alongside other modules.
A _program_ is a C source file (`.c`) that defines `int main(int argc, char
**argv)`. It does not have a corresponding header file because it does not
offer a public interface to other modules.
Because a program might link multiple modules, each module needs to use
globally unique names for its interface (functions, types, global storage). The
canonical way to do this is to use the module name as a prefix for the
interface name: `module_interface`
All functions and storage in a module that are not exposed to other modules in
the interface must be declared `static` in the `.c` file. These are not visible
outside of the module source file, and so do not need a naming convention.
## Tests and mocks
Each module should have a test suite that exercises its public interface. This
template uses [Unity Test](http://www.throwtheswitch.org/unity) to generate
test runner code and provide assertion functions. See the [Unity Assertions
Reference](https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityAssertionsReference.md).
A module's test suite is a C source file in `tests/` whose filename begins with
`test_`. It contains one or more test functions, each one with a name beginning
with `test_`. It should have a `void` parameter specification and a `void`
return type. It is recommended to include the name of the function under test,
the condition of the test, and the expected result in the name of the
`test_...()` function, spelled CamelCase and delimited by underscores.
A test suite can optionally define `void setUp(void)` and `void
tearDown(void)`, to be called before and after each test, respectively.
Each test suite is typically linked with the module under test, along with
_mock modules_ for each dependency of the module under test, generated with
[CMock](http://www.throwtheswitch.org/cmock). Every call that the function
under test makes to a dependency module must be declared with a [CMock
expectation](https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md).
The actual dependency function is not called.
```c
#include "executor/executor.h"
#include "mock_cfgfile.h"
#include "unity.h"
void test_Square_UsesExampleTwo(void) {
cfgfile_func_ExpectAndReturn(7, 49);
int result = executor_doit(7);
TEST_ASSERT_EQUAL_INT(49, result);
}
```
## configure.ac and Makefile.am
[GNU
Autotools](https://www.gnu.org/software/automake/manual/html_node/index.html)
is a suite of packaging tools designed for open source Unix software. It is a
decades-old de facto standard, and is often used for macOS and Windows software
as well. It is typically used to produce a _source distribution_ that is
delivered directly to an end user or system administrator. The user runs
several commands to compile and install the package:
```text
tar xzf coolsoftware-1.0.tar.gz
cd coolsoftware-1.0.tar.gz
./configure
make
sudo make install
```
A GNU Autotools project provides two configuration files: `configure.ac` and
`Makefile.am`. These files describe the requirements and build steps for the
software. The project developer (that's you) uses the command
`autoreconf --install` to set up the environment based on these files, then
runs the `./configure` and `make` steps similar to how an end user would.
This template provides starter code for these files, similar to what's
described in a typical [Autotools
tutorial](https://www.lrde.epita.fr/~adl/autotools.html). You will customize
and extend these files for your project.
## Defining programs in Makefile.am
To create a new program in the project, create a C source file (`.c`) in `src/`
with the name of the program containing a definition for the `int main(int
argc, char **argv)` function.
In `Makefile.am`, add the name of the program to `bin_PROGRAMS`. Create a new
`myapp_SOURCES` definition, where `myapp` is the name of the program. Its value
is the list of `.c` source files for the program and all of the modules in the
project that the program uses.
The example program named `myapp` is defined in `Makefile.am` as follows:
```makefile
bin_PROGRAMS = myapp
myapp_SOURCES = \
$(top_srcdir)/src/myapp.c \
$(top_srcdir)/src/cfgfile.c \
$(top_srcdir)/src/cfgfile.h \
$(top_srcdir)/src/executor.c \
$(top_srcdir)/src/executor.h \
$(top_srcdir)/src/reporter.c \
$(top_srcdir)/src/reporter.h
```
## Defining modules in Makefile.am
A module is just a C source file and header file referenced from another C
source file. The bulk of the set-up is for the test runner program.
To create a new module in the project, create a C source file (`.c`) in `src/`
with the name of the module, and a corresponding C header file (`.h`). Also
create a C source file in `tests/` with the name `test_` followed by the name
of the module and a `.c` extension.
In `Makefile.am`, where `modname` is the name of the module:
1. Add an entry to `check_PROGRAMS` with the name `tests/runners/test_modname`.
2. `tests_runners_test_modname_SOURCES =`
1. The module test source file
2. The module's source and header files
3. `nodist_tests_runners_test_modname_SOURCES =`
1. `tests/runners/runner_test_modname.c`, the runner source to be
generated.
2. `tests/mocks/mock_depname.c` and `.h` where `depname` is the name of a
module that the module under test depends on and should be mocked for
the test, for each dependency.
4. `CLEANFILES += $(nodist_tests_runners_test_modname_SOURCES)`
5. `tests_runners_test_modname_LDADD = libcmock.a`
6. The Makefile rule `tests/test_modname.$(OBJEXT): ...` with the runner `.c`
file and each mock `.c` file as dependencies. This is what causes CMock to
generate the source files prior to the building of the test runner. (Notice
the colon `:` instead of an `=`, because this is a rule, not a definition.)
The example module named `executor` is defined in `Makefile.am` as follows,
with a mocked dependency on the `cfgfile` module:
```makefile
check_PROGRAMS += tests/runners/test_executor
tests_runners_test_executor_SOURCES = \
$(top_srcdir)/tests/test_executor.c \
$(top_srcdir)/src/executor.c \
$(top_srcdir)/src/executor.h
nodist_tests_runners_test_executor_SOURCES = \
tests/runners/runner_test_executor.c \
tests/mocks/mock_cfgfile.c \
tests/mocks/mock_cfgfile.h
CLEANFILES += $(nodist_tests_runners_test_executor_SOURCES)
tests_runners_test_executor_LDADD = libcmock.a
tests/test_executor.$(OBJEXT): \
tests/runners/runner_test_executor.c \
tests/mocks/mock_cfgfile.c
```
## Using Automake
The complete sequence of commands to build all programs is as follows:
```text
autoreconf --install
./configure
make
```
In the example template, this builds the `myapp` program in the project root
directory.
Typically, you only need to use the `make` command after running these commands
for the first time in your project workspace. You only need to run `autoconf`
if `./configure` does not exist, and `./configure` is `Makefile` doesn't exist.
GNU Autotools generates the `Makefile` such that if you change `configure.ac`
or `Makefile.am`, running `make` will regenerate files as needed. If something
seems to be wrong, you can re-run `autoreconf --install` and `./configure` at
any time.
Autotools produces a significant number of auxiliary files in the project
workspace. The template's `.gitignore` file is set up to prevent these from
being committed to your source repo. You may need to add entries to
`.gitignore` if you extend `configure.ac` with new features (such as Libtool).
To clean up the result of `make`:
```text
make clean
```
To clean up the result of `./configure` and `make`:
```text
make distclean
```
By design, there is no built-in way to clean up all of the files generated by
`autoreconf`. I include a script to delete everything ignored by `.gitignore`,
and clean generated files out of `third-party/`. To fully reset the workspace:
```text
python3 scripts/superclean.py
```
To run all unit tests:
```text
make check
```
To run a single unit test:
```text
make check TESTS='tests/runners/test_cfgfile'
```
To build a single tool or a single test runner (without running it):
```text
make myapp
make tests/runners/test_cfgfile
```
The test runners are just programs. You can use them with your debugger. This
template includes VSCode configuration for running a test suite in a debugger
from its source file ("Debug test suite").
To make the source distribution:
```text
make distcheck
```
This builds `myapp-0.1.tar.gz` in the project root directory. This filename is
derived from the first line of `configure.ac`.
It's a good idea to run `make distcheck` after making major changes to
`Makefile.am` just to confirm that the Makefile rules work in isolation of the
current state of the project workspace.
## License
This template and related tools are released under [The
Unlicense](https://unlicense.org/). See [LICENSE](./LICENSE) for complete text.
You are _not_ required to use this license for your project. Replace the
`LICENSE` file with whatever is appropriate.
## Note from the author
My larger
[c-autotools-template](https://github.com/dansanderson/c-autotools-template)
uses a tool (`makemake.py`) that generates `Makefile.am` from proprietary
configuration files. I have mixed feelings about contributing another
proprietary module system to the world, but sometimes it's just the easiest way
to produce the desired result.
I didn't feel right suggesting that that's the best way to do things for all
projects and wanted a functional example that didn't rely on another layer of
code generation. Single-file modules are very common in C projects, and are a
fine place to start.
For what it's worth, I'm also uneasy with how this small template omits a
formalized statement of module dependencies entirely. A program must list every
module in its dependency tree as as source, even if the `main()` source file
for the program only refers to one or two. Each test suite must also list
immediate dependencies as mocks. Overall that's not too bad in terms of module
abstraction leakage or redundant information.
It feels like this could be more concise. `makemake.py` pares it down to a short
list of module dependencies. As far as I can tell, Automake doesn't offer all
of the same macro-like features that a plain Makefile does, so we can't
template this within `Makefile.am` alone. If there's a better way, let me know.
Feedback is welcome! If you have ideas for how this can be improved, fixed, or
made more generally useful, please [file an
issue](https://github.com/dansanderson/c-autotools-template-small/issues).
[Pull
requests](https://github.com/dansanderson/c-autotools-template-small/pulls) are
also welcome, though please pardon me if I take a while to respond.
See also [my blog entry on this
subject](https://dansanderson.com/lab-notes/autotools-in-2022/).
Thanks!
— Dan (contact@dansanderson.com)