Header only library for writing build and test recipes in an opinionated way in C.
The original code started as a fork from nobuild. An entire test framework has been added. An opinionated build strategy has been added. Numerous helper command line options have been added as well.
The idea is that you should not need anything but a C compiler to build a C project. No make, no cmake, no shell, no cmd, no PowerShell etc. Only C compiler. So with the C compiler you bootstrap your build system and then you use the build system to build everything else, and run through all tests.
The framework should be able to make most of the decisions for you, it has easy support for dependency tracking, shared library, and binary packaging. It supports incremental builds and tests, reducing time from code change, to full dependency test execution.
Try it out right here:
First clone the repository.
$ gcc ./nobuild.c -o ./nobuild
$ ./nobuild
Explore nobuild.c file.
After running the example, jump right to the tutorial.
- Simple.
- Reducing the amount of dependencies.
- You get to use C more.
- Built in test framework to go with your built in no build.
- Highly opinionated.
- Doesn't scale with a large project atm. Will need to implement something like buck/bazel to support large projects.
- Doesn't work outside of C/C++ projects.
- You get to use C more.
Keep in mind that nobuild.h is an stb-style header-only library. That means that just including it does not include the implementations of the functions. You have to #define NOBUILD_IMPLEMENTATION
before the include. See our nobuild.c for an example.
- Copy nobuild.h to your project
- Create
nobuild.c
in your project with build recipes. See our nobuild.c for an example. - Bootstrap the
nobuild
executable:$ gcc -O3 ./nobuild.c -o ./nobuild
on POSIX systems
- Initialize your project
$ ./nobuild --init
- Run the build:
$ ./nobuild
nobuild uses feature based development.
add a new feature to your project.
./nobuild --add math
this will automatically create an include file in the include directory include/math.h
, create a directory and file at src/math/lib.c
, and create a new test file named tests/math.c
.
Some features could require additional includes or other linked libraries. Edit the nobuild.c
file, and add the new feature, along with any dependencies.
FEATURE("math","-lpthread");
If math
has any dependencies within your project, include them, and nobuild will automatically link them when building tests.
DEPS("math", "add", "mul", div");
After making any change to your projects nobuild.c
file do not forget to rebuild the nobuild executable $ gcc ./nobuild.c -o ./nobuild
Now, when running an incremental build, and changing the div
feature, just run ./nobuild --build ./src/div/lib.c
or the shorter cli flag ./nobuild -b ./src/div/lib.c
The div
feature will be rebuilt and tested, as well as math
being rebuilt and tested! All cascading dependencies are handled automatically.
You will notice in this repository, the stuff
feature has multiple files. This is called a fat feature. Build times could degrade if you use too many fat features with too many dependencies on other fat features. It is recommended to create many light small single file features for maximum efficiency. this may be deprecated in the future, to save build times.
define the NOBUILD_IMPLEMENTATION
and WITH_MOCKING
preprocessor command at the top of the file, to include the stb style header.
It is recommended to use mocking for everything that is defined outside of the feature, this prevents needing to link every file at test execution for your dependencies, saving test time. Right now, only WITH_MOCKING
is supported.
#define NOBUILD_IMPLEMENTATION
#define WITH_MOCKING
#include "../include/finance.h"
#include "../nobuild.h"
Example tests.
tests/finance.c
#include "../include/finance.h"
DECLARE_MOCK(double, pow, int base COMMA_D int power); //COMMA_D is a macro which expands to __attribute__((unused)), // (,) Comma included. Allowing variable length args.
DECLARE_MOCK(double, mul, int left COMMA_D int right); //This is an oddity with macros and just dealing with C, in order to get mocking right.
DECLARE_MOCK(double, add, int left COMMA_D int right);
// DECLARE_MOCK_VOID(int, ret_2) use _VOID if function takes no arguments.
int main() {
DESCRIBE("finance"); // right now, DESCRIBE("feature") must be the exact name of the feature, to properly bootstrap the tests.
SHOULDB("calculate compound interest", {
future_value_t val = new_compound_interest();
val.r = .03;
val.n = 12;
val.t = 5;
val.p = 10000;
ASSERT(calculate(val) == 11616.17);
});
RETURN(); // special return which documents and handles the errors and passes.
}
In this example, we are testing the finance
feature. It uses our calculations from the math
feature. Notice how we do not bring the math include into scope in our test file
A = P(1 + (r/n))^(n*t)
We know that we need to mock these function calls. I like to do all mocking at the beginning of the SHOULDB
or SHOULDF
macro. Mocking does not have to happen in the order they are ran for different functions.
They do need to be done in order for multiple calls to the same function.
SHOULDB("calculate compound interest with rounding", {
// 10000(1 + (.03 / 12)) ^ (12 * 5)
// the first function calculate completes is .03 / 12
// you do not need to mock every function in order, just the same functions that are used multiple times.
MOCK(div, .0025);
MOCK(add, 1.0025);
MOCK(mul, 60);
MOCK(pow, 1.16161678156);
MOCK(mul, 11616.1678156);
future_value_t val = new_compound_interest();
val.r = .03;
val.n = 12;
val.t = 5;
val.p = 10000;
ASSERT(calculate(val) == 11616.17);
}
The only two functions where order is important is that the MOCK(mul, 11616.1678156)
is done any time after MOCK(mul, 60)
.
Although this is a contrived example, and we are mocking very simple things. It shows the separation of concerns between what you are mocking, and what logic is done in the calculate
method. The only logic not done in the math
feature is rounding the output to the nearest penny.
visit the demo tutorial
visit windows windows