craftc is a small command-line build tool for C projects. It compiles object
files, creates static libraries, and links executables. It is designed to be
called directly or from tools such as Make and Task.
It tracks source files, headers, and command-line changes so unchanged targets do not need to be rebuilt.
- Rebuilds objects when a source file, header, or compile command changes
- Compiles multiple source files concurrently
- Creates static libraries with
ar - Links executables and relinks when an input library changes
- Accepts custom compiler and linker flags
- Provides optional verbose output
Linux is supported. macOS and Windows have not been tested.
For each object file, craftc checks whether:
- The object file exists
- Its dependency file exists
- Its recorded compile command exists
- The source file is newer than the object
- Any recorded project dependency is newer than the object
- The compile command, compiler, or compiler flags have changed
When linking an executable, craftc checks whether:
- The executable exists
- Any application object was rebuilt during the current run
- Its recorded link command exists
- Any linked library is newer than the executable
- The link command, compiler, library list, or linker flags have changed
A static library is recreated when:
- The archive does not exist
- Any of its object files was rebuilt during the current run
- A forced build was requested
Passing --force bypasses the up-to-date checks and rebuilds the requested
target completely.
This module was previously published as:
github.com/ameergituser/craftc
New versions are published under:
github.com/ameershira/craftc
Install with Go:
go install github.com/ameershira/craftc@latestOr clone and build it locally:
git clone https://github.com/ameershira/craftc
cd craftc
go build .These examples use -v to print the commands being run.
Use the obj command:
craftc obj -cc cc -cfile ./libsrc1.c -cflags "-Wall -O2" -objdir ./build/obj -vOutput:
[build] 🧠 build/obj/libsrc1.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -O2 -MMD -MF build/obj/libsrc.libsrc1.c.d -c ./libsrc1.c -o build/obj/libsrc1.c.oRe-run:
✅ build/obj/libsrc1.c.o is up to date.Use the objs command:
craftc objs -cc cc -cfiles "./libsrc1.c ./libsrc2.c" -cflags "-Wall" -objdir ./build/obj -vOutput:
[build] 🧠 build/obj/libsrc2.c.o: object file does not exist.
[build] 🧠 build/obj/libsrc1.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -MMD -MF build/obj/libsrc2.c.d -c ./libsrc2.c -o build/obj/libsrc2.c.o
[compile] 🔨 /usr/bin/cc -Wall -MMD -MF build/obj/libsrc1.c.d -c ./libsrc1.c -o build/obj/libsrc1.c.oUse the static-lib command:
craftc static-lib -cc cc -cfiles "./libsrc1.c ./libsrc2.c" -cflags "-Wall -O2" -lib-path "./build/lib.a" -objdir ./build/obj -vOutput:
[build] 🧠 build/obj/libsrc2.c.o: object file does not exist.
[build] 🧠 build/obj/libsrc1.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -O2 -MMD -MF build/obj/libsrc2.c.d -c ./libsrc2.c -o build/obj/libsrc2.c.o
[compile] 🔨 /usr/bin/cc -Wall -O2 -MMD -MF build/obj/libsrc1.c.d -c ./libsrc1.c -o build/obj/libsrc1.c.o
[archive] 📦 /usr/bin/ar rcs ./build/lib.a build/obj/libsrc1.c.o build/obj/libsrc2.c.oRe-run:
✅ build/obj/libsrc2.c.o is up to date.
✅ build/obj/libsrc1.c.o is up to date.
✅ 📦 ./build/lib.a is up to date.This example also links a static library:
craftc exe -cc cc -cfiles "main.c" -objdir ./build/obj -cflags "-Wall -O2 -I ./libsrc" -lib-paths "./build/lib.a" -exe-path ./appOutput:
[build] 🧠 build/obj/main.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -O2 -I ./libsrc -MMD -MF build/obj/main.c.d -c ./main.c -o build/obj/main.c.o
[linking] 🔗 /usr/bin/cc build/obj/main.c.o ./build/lib.a -o ./build/appRe-run:
✅ build/obj/main.c.o is up to date.
✅ 🚀 ./build/app is up to date.Each command can be used on its own, which makes craftc straightforward to
call from Task or another task runner. This Taskfile
builds a static library before linking the executable:
version: '3'
tasks:
lib:
desc: Build a static library with a few source files
vars:
BUILD_DIR: ./build/{{.TASK}}
OBJ_DIR: '{{.BUILD_DIR}}/obj'
SRC: ./libsrc/libsrc1.c ./libsrc/libsrc2.c
STATIC_LIB: '{{.BUILD_DIR}}/{{.TASK}}.a'
CFLAGS: -Wall -O2
cmds:
- ./craftc static-lib -cc cc -cfiles "{{.SRC}}" -objdir {{.OBJ_DIR}} -cflags "{{.CFLAGS}}" -lib-path "{{.STATIC_LIB}}" -i {{.CLI_ARGS}}
exe:
desc: Build an exe cmd with a few source files
vars:
BUILD_DIR: ./build/{{.TASK}}
OBJ_DIR: '{{.BUILD_DIR}}/obj'
SRC: ./appsrc/main.c
STATIC_LIB: ./build/lib/lib.a
CFLAGS: -Wall -O2 -I ./libsrc
# LDFLAGS: -Wl,--trace
APP_PATH: '{{.BUILD_DIR}}/{{.TASK}}-app'
deps:
- task: lib
cmds:
- >
./craftc exe
-cc cc
-cfiles "{{.SRC}}"
-objdir {{.OBJ_DIR}}
-cflags "{{.CFLAGS}}"
-ldflags "{{.LDFLAGS}}"
-exe-path {{.APP_PATH}}
-lib-paths "{{.STATIC_LIB}}"
-i {{.CLI_ARGS}}Output:
[build] 🧠 build/lib/obj/libsrc.libsrc2.c.o: object file does not exist.
[build] 🧠 build/lib/obj/libsrc.libsrc1.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -O2 -MMD -MF build/lib/obj/libsrc.libsrc1.c.d -c ./libsrc/libsrc1.c -o build/lib/obj/libsrc.libsrc1.c.o
[compile] 🔨 /usr/bin/cc -Wall -O2 -MMD -MF build/test4/obj/libsrc.libsrc2.c.d -c ./libsrc/libsrc2.c -o build/lib/obj/libsrc.libsrc2.c.o
[archive] 📦 /usr/bin/ar rcs ./build/lib/lib.a build/lib/obj/libsrc.libsrc1.c.o build/lib/obj/libsrc.libsrc2.c.o
[build] 🧠 build/exe/obj/appsrc.main.c.o: object file does not exist.
[compile] 🔨 /usr/bin/cc -Wall -O2 -I ./libsrc -MMD -MF build/exe/obj/appsrc.main.c.d -c ./appsrc/main.c -o build/exe/obj/appsrc.main.c.o
[linking] 🔗 /usr/bin/cc build/exe/obj/appsrc.main.c.o ./build/lib/lib.a -o ./build/exe/appRe-run:
✅ build/lib/obj/libsrc.libsrc2.c.o is up to date.
✅ build/lib/obj/libsrc.libsrc1.c.o is up to date.
✅ 📦 ./build/lib/lib.a is up to date.
✅ build/exe/obj/appsrc.main.c.o is up to date.
✅ 🚀 ./build/exe/app is up to date.