Skip to content

Header only library for writing build recipes in C.

License

Notifications You must be signed in to change notification settings

coffeebe4code/nobuild

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nobuild

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.

Main idea

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.

Begin

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.

Advantages of nobuild

  • Simple.
  • Reducing the amount of dependencies.
  • You get to use C more.
  • Built in test framework to go with your built in no build.

Disadvantages of noframework

  • 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.

How to use the library in your own project

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.

  1. Copy nobuild.h to your project
  2. Create nobuild.c in your project with build recipes. See our nobuild.c for an example.
  3. Bootstrap the nobuild executable:
    • $ gcc -O3 ./nobuild.c -o ./nobuild on POSIX systems
  4. Initialize your project
    • $ ./nobuild --init
  5. Run the build: $ ./nobuild

Feature based development

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.

Using the test framework

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.

Tutorial

visit the demo tutorial

Windows Setup

visit windows windows

About

Header only library for writing build recipes in C.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C 100.0%