This tutorial will take you from a new github project and provide a good overview of the nobuild
framework, with real examples.
If you don't already have a repository cloned or created locally. Follow this guide. Github
Copy the ./nobuild.h
file found at the root of this repository nobuild.h to the root of your repository.
Copy the starter ./demo/nobuild.c
file found in this demo folder nobuild.c to the root of your repository.
- the demo contents should appear as follows.
./nobuild.c
#define NOBUILD_IMPLEMENTATION
#define CFLAGS "-Wall", "-Werror", "-std=c11"
#include "./nobuild.h"
int main(int argc, char **argv) {
BOOTSTRAP(argc, argv);
return 0;
}
- if you wish to use a different compiler you can add another line
#define CC "clang"
The file structure should appear as below.
> ls -a
.git
.gitignore
LICENSE
README.md
nobuild.h
nobuild.c
- you may or may not have the LICENSE, .gitignore, and README depending on if you initialized your own git repo, or did it through github in the browser.
Build nobuild
. You will have to do this any time, you modify your ./nobuild.c
file!
> gcc ./nobuild.c -o ./nobuild
Initialize your repository as a nobuild
project.
> ./nobuild --init
[INFO] MKDIRS: target/nobuild
[INFO] MKDIRS: obj
[INFO] MKDIRS: exes
[INFO] MKDIRS: src
[INFO] MKDIRS: tests
[INFO] MKDIRS: include
- this created a few directories.
src
will contain all your c source files.tests
will contain all your test files.obj
will contain all your object files generated by your C Compiler.exes
will contain all your source c files that become executables.target
will contain all your deliverable or meaningful deployments. Binaries, shared libraries, static libraries, tests, etc.include
will contain all your include files.
Add your first feature.
> ./nobuild --add hello
[INFO] MKDIRS: include
[INFO] CMD: touch include/hello.h
[INFO] MKDIRS: src/hello
[INFO] CMD: touch src/hello/lib.c
[INFO] MKDIRS: tests
[INFO] CMD: touch tests/hello.c
- this created a new folder in the
src
directory, a test file, an include file, and an empty c file. - you will notice that the output has a lot of the same information when we ran
--init
. This is because we are being safe and making sure that those folders exist, if not, they are created. We can suppress[INFO]
logs by adding#define NOINFO
at the top of ournobuild.c
file.
Modify your ./nobuild.c
accordingly. Use your editor of choice.
./nobuild.c
#define NOBUILD_IMPLEMENTATION
#define CFLAGS "-Wall", "-Werror", "-std=c11"
// optionally define NOINFO, all info logs will be present in the tutorial
#define NOINFO
#include "./nobuild.h"
int main(int argc, char **argv) {
FEATURE("hello"); // only required line for this step of tutorial.
BOOTSTRAP(argc, argv);
return 0;
}
- sometimes, you might have additional dependencies defined outside of the project like
sdl2
orboost
or much more commonly,pthread
.nobuild
doesn't add this FEATURE automatically tonobuild.c
. (maybe future releases).- here is where you would add extra linking dependencies.
FEATURE("hello","-lpthread");
. This is saying: any time you build a deployable, that uses hello, you need to havepthread
linked with it.
- here is where you would add extra linking dependencies.
- run
gcc ./nobuild.c -o ./nobuild
. I have a simple key press combo bound to do this in my editor. You can do similar in your editor.
Run ./nobuild
> ./nobuild
[WARN] No arguments passed to nobuild
[WARN] Building all features
[INFO] MKDIRS: target/nobuild
[INFO] MKDIRS: obj
[INFO] MKDIRS: obj/hello
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -fPIC -o obj/hello/lib.o -c src/hello/lib.c
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -o target/hello tests/hello.c obj/hello/lib.o
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status
[ERRO] command exited with exit code 1
- you will get errors from your compiler if your code has an issue. This issue here, is describing how the
tests/hello.c
file does not have a main function.
Update your include file, library file, and test file.
./include/hello.h
#pragma once
int show_hello();
./src/hello/lib.c
#include <stdio.h>
int show_hello() {
puts("Hello");
return 0;
}
./tests/hello.c
#define NOBUILD_IMPLEMENTATION
#include "../include/hello.h"
#include "../nobuild.h"
#include <stdio.h>
void test_add() { ASSERT(2 + 2 == 4); }
int main() {
DESCRIBE("hello");
SHOULDF("add 2 + 2", test_add);
SHOULDB("compare two strings", { ASSERT_STR_EQ("hello", "hello"); });
RETURN();
}
Run ./nobuild --release
or ./nobuild -r
. All commands come with a short flag.
> ./nobuild -r
[INFO] MKDIRS: target/nobuild
[INFO] MKDIRS: obj
[INFO] MKDIRS: obj/hello
[INFO] CMD: gcc -Wall -Werror -std=c11 -O3 -fPIC -o obj/hello/lib.o -c src/hello/lib.c
[INFO] CMD: gcc -Wall -Werror -std=c11 -O3 -o target/hello tests/hello.c obj/hello/lib.o
[INFO] CMD: target/hello
[INFO] DESCRIBE: tests/hello.c => hello
[RUN!] It should... add 2 + 2
[OKAY] Passed
[RUN!] It should... compare two strings
[OKAY] Passed
[INFO] NOBUILD took ... 0.000370 sec
[INFO] OKAY: tests passed 2
[INFO] FAIL: tests failed 0
[INFO] TOTAL: tests ran 2
- success!.
./nobuild
with no arguments will assume you want the debug version. You can be explicit with./nobuild -d
- SHOULDB takes an inline body, SHOULDF takes a function name. Allowing you to break up your code and better read the flow in main.
- We can run and debug our tests, independently. All tests are created as an executable, try it.
./target/hello
. If you wanted debug symbols included, runnobuild
with the debug flag../nobuild -d
Right now, our hello feature, is just an object file. Let's make it an executable and a shared library. This will allow others to execute our amazing hello program, or use hello
as a library in their own project.
> ./nobuild --exe holler
[INFO] MKDIRS: exes
[INFO] CMD: touch exes/holler.c
./exes/holler.c
#include "../include/hello.h"
int main() {
show_hello();
}
./nobuild.c
#define NOBUILD_IMPLEMENTATION
#define CFLAGS "-Wall", "-Werror", "-std=c11"
#include "./nobuild.h"
int main(int argc, char **argv) {
FEATURE("hello");
LIB("hello");
EXE("holler", "hello");
BOOTSTRAP(argc, argv);
return 0;
}
- after running
gcc ./nobuild.c -o ./nobuild
. You can run./nobuild
once again.
> ./nobuild -d
[INFO] MKDIRS: target/nobuild
[INFO] MKDIRS: obj
[INFO] MKDIRS: obj/hello
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -fPIC -o obj/hello/lib.o -c src/hello/lib.c
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -shared -o target/libhello.so obj/hello/lib.o
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -o target/hello tests/hello.c obj/hello/lib.o
[INFO] CMD: target/hello
[INFO] DESCRIBE: tests/hello.c => hello
[RUN!] It should... add 2 + 2
[OKAY] Passed
[RUN!] It should... compare two strings
[OKAY] Passed
[INFO] CMD: gcc -Wall -Werror -std=c11 -g -O0 -o target/holler exes/holler.c obj/hello/lib.o
[INFO] NOBUILD took ... 0.000564 sec
[INFO] OKAY: tests passed 2
[INFO] FAIL: tests failed 0
[INFO] TOTAL: tests ran 2
- we now have an executable which works exactly like we expected. Run it with
./target/holler
- we also have a library that was placed in target. Distribute it to all your friends.
- !NOTE! as of right now, if your exe has the same name as a feature. that features tests can't be ran independently.
The bulk of the tutorial is complete. This may seem a bit much. I rely on shortcuts in my editor to accomplish everything without any thought. see the demo in vim or vscode to get an idea how painless using nobuild
is. See a complete list of options to read up on all commands, macros, and flags possible. For preparing your code to production see cicd
The next part of the tutorial will cover the coolest feature in nobuild
the incremental build.