.vscode | ||
scripts | ||
src | ||
tests | ||
third-party | ||
.gitignore | ||
.gitmodules | ||
configure.ac | ||
LICENSE | ||
Makefile.am | ||
README.md |
c-autotools-template-small
This is template for new C projects using GNU Autotools, Unity Test, and CMock, with one C file per module.
I originally wrote
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 to generate test runner code and provide assertion functions. See the Unity Assertions Reference.
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. Every call that the function under test makes to a dependency module must be declared with a CMock expectation. The actual dependency function is not called.
#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 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:
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. 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:
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:
- Add an entry to
check_PROGRAMS
with the nametests/runners/test_modname
. tests_runners_test_modname_SOURCES =
- The module test source file
- The module's source and header files
nodist_tests_runners_test_modname_SOURCES =
tests/runners/runner_test_modname.c
, the runner source to be generated.tests/mocks/mock_depname.c
and.h
wheredepname
is the name of a module that the module under test depends on and should be mocked for the test, for each dependency.
CLEANFILES += $(nodist_tests_runners_test_modname_SOURCES)
tests_runners_test_modname_LDADD = libcmock.a
- 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:
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:
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
:
make clean
To clean up the result of ./configure
and make
:
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:
python3 scripts/superclean.py
To run all unit tests:
make check
To run a single unit test:
make check TESTS='tests/runners/test_cfgfile'
To build a single tool or a single test runner (without running it):
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:
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. See 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
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. Pull requests are also welcome, though please pardon me if I take a while to respond.
See also my blog entry on this subject.
Thanks!
— Dan (contact@dansanderson.com)