makemake
This repository contains a makefile for C projects. It is configured out-of-the-box for C17 using GCC, enforces extremely strict compilation standards (treating all warnings as errors), manages out-of-source builds, and includes automatic dependency tracking and versioning.
Setup Tutorial
Follow these steps to bootstrap your project from scratch using this Makefile:
1. Initialize the Project
Place the Makefile in the root of your new (and empty) project directory. Note that the output executable will be named after your root directory.
Run the following command to scaffold the required directory structure:
make init
What this does: Creates the config/, includes/, output/, and source_code/ directories. It also generates a starter main.c and initializes the versioning and file list configuration files.
2. Write Your Code
- Place all your
.cfiles inside thesource_code/directory (or subdirectories within it). - Place all your
.hfiles inside theincludes/directory.
3. Synchronize the Build System
Whenever you add, rename, or delete a .c or .h file, you must tell the Makefile to update its tracking lists:
make synch
What this does: Scans your directories and updates config/source_code_list and config/header_list.
4. Build the Project
Compile your code for the first time in debug mode:
make debug
Your compiled executable will be located in the output/ directory, named <your_folder_name>_00.000.00.0000 (or whatever the current version string is).
Make Targets Overview
Here is a detailed breakdown of every command available in this Makefile, what it does, and when to use it.
| Target | Description & Use Case |
|---|---|
make / make help |
Behavior: Prints a help message with available commands. |
When to use: When you need a quick reminder of the available targets. |
| make init | Behavior: Scaffolds the project directories (source_code, includes, config, output) and creates base tracker files.
When to use: Only once, at the very beginning of a new project. |
| make synch | Behavior: Searches the project for .c and .h files and writes their paths to config/source_code_list and config/header_list.
When to use: Every time you create a new file, delete a file, or rename a file. (You do not need to run this if you only edited the contents of an existing file). |
| make debug | Behavior: Compiles the project incrementally using DEBUG_FLAGS (-g3, -O0, Undefined Behavior Sanitizer).
When to use: During active development. This build includes debug symbols for gdb, disables optimization for accurate stepping, and catches undefined behavior at runtime. |
| make fast | Behavior: Compiles the project incrementally using RELEASE_FLAGS (-O2, NDEBUG) without bumping the project version.
When to use: When you want to test the performance of the optimized build locally, but you aren't ready to officially "release" a new version yet. |
| make release | Behavior: Bumps the project version using a date/time stamp (Format: _YY.DAY.HH.MMSS), saves it to config/version, and compiles the project using RELEASE_FLAGS.
When to use: When your code is tested, stable, and ready to be packaged or deployed. |
| make clean | Behavior: Deletes the output/ directory, removing all compiled object files (.o), dependency files (.d), and executables.
When to use: When you want a completely fresh build, or if you change compiler flags in the Makefile and need to force a full recompilation. |
Project Structure
After running make init and building, your project will look like this:
my_project/ # Root folder (dictates the executable name)
├── Makefile # The build script
├── config/ # Build tracking files (auto-generated)
│ ├── header_list
│ ├── source_code_list
│ └── version
├── includes/ # Place all your .h files here
├── source_code/ # Place all your .c files here
│ └── main.c
└── output/ # Build artifacts (ignored by git ideally)
├── obj_debug/ # .o and .d files for debug builds
├── obj_release/ # .o and .d files for release builds
└── my_project_version # The final compiled executables
Configuration & Defaults
This Makefile is heavily configured for safety and strictness. Here is how to modify the defaults to suit your needs:
1. Changing the C Standard
The project defaults to C17. To change this to C11, C99, or C2x:
- Open the
Makefile. - Locate the
CFLAGS = -std=c17 \line. - Change
-std=c17to-std=c11(or your preferred standard).
2. Changing the Compiler
The default compiler is GCC. To use Clang (which is highly recommended for macOS users or for its excellent static analyzer):
- Locate
CC = gccat the top of the file. - Change it to
CC = clang.
3. Modifying Warning Strictness (Fixing Compilation Blocks)
By default, this Makefile includes -Werror, which treats all warnings as errors and halts compilation. It also includes an extensive list of strict warnings (like -Wconversion, -Wshadow, -Wpadded, etc.).
- To allow warnings to pass without failing the build: Remove the
-Werror \line from theCFLAGSblock. - To remove specific strict checks: Simply delete or comment out the specific flag (e.g.,
-Wmissing-prototypes \) from theCFLAGSlist.
4. Modifying Debug and Release Flags
You can tweak optimization levels and sanitizers at the top of the file:
- Debug (
DEBUG_FLAGS): Defaults to-g3 -O0 -fsanitize=undefined -fno-omit-frame-pointer. You can add Address Sanitizer by appending-fsanitize=address. - Release (
RELEASE_FLAGS): Defaults to-O2 -D NDEBUG. (NDEBUGstrips outassert()calls). If you want maximum optimization, you can change-O2to-O3or-Ofast(use-Ofastwith caution as it breaks strict IEEE compliance for math).