3

The lib folder contains many *.h and *.c files, and all files are in development phrase and they contain many errors including syntax errors. So I want to write the Makefile to debug all libraries one by one.

Here is the project folder structure (All *.c files contain the "main" function for testing purpose):

./Makefile

./src/lib/hello.h
./src/lib/hello.c
./src/lib/world.h
./src/lib/world.c
./src/lib/foo.h
./src/lib/foo.c
./src/lib/bar.h
./src/lib/bar.c

Here is the Makefile_hello for debugging "hello.h" and "hello.c":

CC = gcc
CFLAGS = -Wall -g
# How to include only "hello.h"?
INCLUDES = -Isrc/lilb
# How to write the rest for debugging only "hello.c"?

Here is the Makefile_world for debugging "world.h" and "world.c":

CC = gcc
CFLAGS = -Wall -g
# How to include only "world.h"?
INCLUDES = -Isrc/lilb
# How to write the rest for debugging only "world.c"?

How to write the correct Makefile?

2
  • Don't change the makefile. Just make src/lib/world.o Commented Oct 26 at 11:53
  • How can you build the production executable, if all the modules have a main() function? You might want to edit your question to clarify and optimally add a minimal reproducible example. Commented Oct 26 at 13:20

3 Answers 3

1
CC = gcc
CFLAGS = -Wall -g
# How to include only "hello.h"?
INCLUDES = -Isrc/lilb
# How to write the rest for debugging only "hello.c"?

First of all you don't need to specify explicitly the compiler you are going to use. Make uses default options that will start cc which is normally an alias of the compiler you use in your environment. This is also true for CFLAGS but, as the default flags are not the ones you state, you need to specify them. This also affects the include directories where you save your files.

Next you need to specify a set of dependency commands between source, compiler (.o files) and the final executable. As you have several files conforming the final executable you will need to specify the dependencies between the executable and the .o files from which it is made, and the dependencies from the .c sources and the .h sources included by the .c files (unfortunately you don't specify which .h files you include in each .c and .h files. I will assume some arrangement, based on my experience)

As an example, and to give you the most spread variety of cases I will assume that each source file source.c will #include <source.h> and that the foo compilation unit includes bar.h and hello and world both include foo.h.

hello.c  -- hello.h
          + world.h
          + foo.h

world.c  -- world.h
          + foo.h

foo.c    -- foo.h
          + bar.h

bar.c    -- bar.h

We will start with the dependencies of hello_world (I will use variables to avoid repetitions):

hello_world_objs   = hello.o world.o foo.o bar.o

hello_world: $(hello_world_objs)
    $(CC) $(LDFLAGS) -o $@ $(hello_world_objs)

Which will link all the object files into a new executable in case any of them changes.

Next, we need to specify the dependencies of the object files. I will write one dependency only as the make tool has some implicit rules that allow you to compile only specifying the object file and the source file:

foo.o: foo.c foo.h bar.h
    $(CC) $(CFLAGS) -c -o $@ foo.c

This rule before is implicitly included in make by indicating a series of suffixes in the makefile and the dependencies between them. As I say, you don't need to create the automatic dependency because it is included by default in make:

.c.o:   # dependency of a .o file from a .c file
    $(CC) $(CFLAGS) -c -o $@ $<

You will see there are special variable names that allow you to abreviate the target ($@) and the dependent file ($<) in the command line.

If you have special dependencies you want to specify but the normal case is to compile a source using the implicit command, you can specify that in a rule that has no commands below, like:

hello.o: hello.c hello.h world.h
world.o: world.c world.h foo.h
foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h

all these files will be compiled each with the default routing, in case the source or any of the header files it includes is changed.

Finally, as you have seen, the variable CFLAGS is included in the command of the implicit rule to compile a file, but the INCLUDES variable is not. In order to compile all files specifying the includes directory, you need to add the INCLUDES variable contents into the CFLAGS with

CFLAGS += $(INCLUDES) 

so finally your Makefile will be:

(A final note: I have added the compilation option -O0 to inhibit all optimizations, as if you are going to debug your code you will need to deactivate optimizations, so your compiled code reflects exactly what you wrote in the source. Or you will see strange things when running the compiler over optimized code.)

# Makefile -- script to build hello_world
# Author: Put your name here.
# Date: Mon Oct 27 10:31:48 EET 2025
# Copyright: (C) 2025 your name again here.
# License: bla bla.

CFLAGS           = -Wall -g -O0
LDFLAGS          = -g
INCLUDES         = -I src/lilb
CFLAGS          += $(INCLUDES)  # you don't need INCLUDES if you had
                                # added the includes directory directly
                                # to CFLAGS

hello_world_objs = hello.o world.o foo.o bar.o

hello_world: $(hello_world_objs)
    $(CC) $(LDFLAGS) -o $@ $($@_objs)

# Explicit dependencies between files, to be compiled with
# the implicit .c --> .o  make rule
# .c.o:
#     $(CC) $(CFLAGS) -c -o $@ $<

hello.o: hello.c hello.h world.h
world.o: world.c world.h foo.h
foo.o: foo.c foo.h bar.h
bar.o: bar.c bar.h
Sign up to request clarification or add additional context in comments.

Comments

0
// main.rs
// Rustified Makefile, just to ruin a C purist’s day.
// Author: you know damn well who.
// License: Undefined Behavior.

use std::process::Command;

fn main() {
    let sources = ["hello", "world", "foo", "bar"];
    let includes = "-Isrc/lilb";
    let cflags = "-Wall -g -O0";
    let compiler = "gcc";

    println!("🔧 Compiling just to irritate Makefile zealots...");

    for src in &sources {
        let cfile = format!("{src}.c");
        let ofile = format!("{src}.o");

        let output = Command::new(compiler)
            .args(cflags.split_whitespace())
            .args(includes.split_whitespace())
            .args(["-c", &cfile, "-o", &ofile])
            .output()
            .expect("failed to execute compile command");

        if !output.status.success() {
            eprintln!(
                "❌ Failed compiling {cfile}: {}",
                String::from_utf8_lossy(&output.stderr)
            );
            std::process::exit(1);
        } else {
            println!("✅ Built {ofile}");
        }
    }

    println!("🔗 Linking like it’s 1999...");
    let objs: Vec<String> = sources.iter().map(|s| format!("{s}.o")).collect();
    let output = Command::new(compiler)
        .args(["-g", "-o", "hello_world"])
        .args(&objs)
        .output()
        .expect("failed to link");

    if !output.status.success() {
        eprintln!(
            "❌ Linking failed: {}",
            String::from_utf8_lossy(&output.stderr)
        );
        std::process::exit(1);
    }

    println!("🎉 Build complete: ./hello_world");
}

// build.rs
// The Ouroboros Build: Cargo builds Rust builds Make builds Cargo.
// Author: Chaos, embodied.

use std::process::Command;

fn main() {
    println!("cargo:rerun-if-changed=Makefile");

    // Step 1: Run make (which compiles your C code)
    println!("🌀 Cargo is summoning Make...");
    let make_status = Command::new("make")
        .status()
        .expect("Failed to run make");

    if !make_status.success() {
        eprintln!("💀 Make choked on its own dependencies.");
        std::process::exit(1);
    }

    // Step 2: Trigger Cargo *again* (yes, itself)
    println!("♻️ Cargo will now... rebuild itself?");
    let cargo_again = Command::new("cargo")
        .args(["build", "--quiet"])
        .status()
        .expect("Failed to recursively invoke Cargo");

    if !cargo_again.success() {
        eprintln!("☠️ Recursive Cargo failed. You angered the compiler gods.");
        std::process::exit(1);
    }

    println!("🎉 Ouroboros complete: Rust built Make built Rust.");
}
# Makefile
# Step 2 of the infinite loop.

CC = gcc
CFLAGS = -Wall -g -O0
SRC = hello.c
OUT = hello

all: $(OUT)
    @echo "🧱 Make just built C. Now calling Cargo again..."
    cargo build --quiet || true

$(OUT): $(SRC)
    $(CC) $(CFLAGS) -o $@ $<

[package]
name = "ouroboros-build"
version = "0.1.0"
edition = "2021"
build = "build.rs"

[dependencies]

How To Run (For C Neckbeards)
cargo build

Comments

-1
# ==========================================================
#   Makefile for Modular C Project with Debug Selector
#   Usage:
#     make                → build all modules (debug)
#     make lib=foo        → build only foo.c / foo.h
#     make mode=release   → build all in release mode
#     make clean          → remove build artifacts
# ==========================================================

# Compiler and flags
CC       := gcc
MODE     ?= debug
CFLAGS   := -Wall -std=c17
INCLUDES := -Isrc/lib
BUILD    := build

ifeq ($(MODE),debug)
  CFLAGS += -g -O0
else
  CFLAGS += -O2 -DNDEBUG
endif

# Source discovery
SRC_DIR  := src/lib
SRC_ALL  := $(wildcard $(SRC_DIR)/*.c)
OBJ_ALL  := $(patsubst $(SRC_DIR)/%.c,$(BUILD)/%.o,$(SRC_ALL))
EXE      := app

# Optional single library
LIB      ?=

ifeq ($(LIB),)
  SRC := $(SRC_ALL)
  OBJ := $(OBJ_ALL)
  TARGET := $(EXE)
else
  SRC := $(SRC_DIR)/$(LIB).c
  OBJ := $(BUILD)/$(LIB).o
  TARGET := $(BUILD)/$(LIB)_test
endif

# Build rules
.PHONY: all clean dirs

all: dirs $(TARGET)

dirs:
    @mkdir -p $(BUILD)

$(EXE): $(OBJ)
    $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^

$(BUILD)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/%.h
    $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@

$(BUILD)/%_test: $(BUILD)/%.o
    $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^

clean:
    rm -rf $(BUILD) $(EXE)

8 Comments

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
While you are editing your answer to add some explanations, please add "make" to the block of code to help the highlighter.
man,, I wrote that make file for you, did you like it :-)
Do it yourselves then. I'm out!!! STACKOVERREDDIT
Well, as a nice human you took the tour and read some pages of the help center, didn't you? That's perfect and rare, TBH. And so you know for sure that comments are just targeting your post, not you as a person. I'm really sorry that you feel that way but if I did it myself, you would not have found that it is possible. The main issue is not the missing language marker, but the missing explanation. Have a nice day!
No. I didn't rtfm. I like to touch grass. Might help with that Vitamin D Deficiency if you touch grrass in the sun.
Well, not for me, but the questioner and any future visitor seeking help. ;-) I'm quite firm with makefiles. Therefore it is a sign of quality to add explanations for the not-so-enlightened.
"I didn't rtfm." So you will have a hard time playing with tech people and the technology, as this needs to know the rules and to behave. ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.