Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/fernandodanielmaqueda/gcc-bison-flex-GNUmakefile/llms.txt

Use this file to discover all available pages before exploring further.

A single GNUmakefile can build any number of programs, libraries, and shared libraries at once. Each declared binary gets its own source directory, source list, linking order, compiler flags, run arguments, and working directory — all fully isolated from the others. This pattern works well for projects with multiple utilities in one repository, client/server pairs, programs sharing a common library, or test suites alongside the main program.

Declaring multiple programs

Append each program basename to BIN_PROGRAMS:
BIN_PROGRAMS += server
BIN_PROGRAMS += client
Or on a single line:
BIN_PROGRAMS += server client
The framework generates a complete set of build, run, and debug targets for each entry: make all, make run-server, make run-client, make gdb-server, make valgrind-memcheck-client, and so on.

Per-program isolation

Every variable that controls a binary is scoped by prefixing the binary’s basename. The following variables are independent for each program:
VariablePurpose
$(prog)_SRCDIRDirectory to search for source files
$(prog)_SOURCESList of source files to compile
$(prog)_LDADDLinking order (objects, libraries, flags)
$(prog)_CPPFLAGSPreprocessor flags (appended to global CPPFLAGS)
$(prog)_CFLAGSCompiler flags (appended to global CFLAGS)
$(prog)_ASFLAGSAssembler flags (appended to global ASFLAGS)
$(prog)_LDFLAGSLinker flags (appended to global LDFLAGS)
$(prog)_ARGSCommand-line arguments for make run-$(prog)
$(prog)_CWDWorking directory for make run-$(prog)
Global variables like CFLAGS, CPPFLAGS, and LDLIBS apply to all binaries. Use per-program variables for anything that should differ between them.

Source auto-discovery per program

Each program’s _FIND_SOURCES runs an independent FIND command scoped to its own _SRCDIR. Programs with different source directories never pick up each other’s files:
$(server)_SRCDIR := src/server/
$(client)_SRCDIR := src/client/

$(server)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(server)_SRCDIR),'$($(server)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

$(client)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(client)_SRCDIR),'$($(client)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

Include path (-I) and library path (-L) auto-discovery

Each program has its own _FIND_-I_FLAGS and _FIND_-L_FLAGS evaluated against its own _INCLUDEDIR and _LIBDIR. This means a program only picks up headers and libraries from its own subtree unless you explicitly add paths from outside:
$(server)_INCLUDEDIR := $($(server)_SRCDIR)
$(server)_FIND_-I_FLAGS := $(sort $(shell $(FIND) \
    $(if $($(server)_SRCDIR),'$($(server)_INCLUDEDIR)',.) \
    -type f -name '*.h' -print \
  | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
           -e "s?^?-I'?" -e "s?$$?'?" \
           -e "s?^-I''$$?-I'.'?" ;))
$(server)_CPPFLAGS += $($(server)_FIND_-I_FLAGS)

Building specific programs vs all

# Build every program and library declared in the makefile
make all

# The framework does not generate individual targets named after programs
# by default — use make all, which is always the default goal
make all
To rebuild only one program, touch or modify one of its source files and run make all. The incremental dependency tracking system ensures only the affected binary is relinked.

Running and debugging individual programs

# Run a specific program
make run-server
make run-client

# Debug with GDB
make gdb-server
make gdb-client

# Memory analysis with Valgrind
make valgrind-memcheck-server
make valgrind-memcheck-client

# Valgrind with the none (no-tool) passthrough
make valgrind-none-client
Each run- and gdb- target uses the program’s _CWD and _ARGS settings.

Complete 2-program example

This example builds a server and a client program from separate source directories. Project layout:
myproject/
├── GNUmakefile
├── mkframework/
├── src/
│   ├── server/
│   │   ├── main.c
│   │   └── handler.c
│   └── client/
│       ├── main.c
│       └── request.c
├── bin/
│   ├── server
│   └── client
└── obj/
    └── src/
        ├── server/
        │   ├── main.o
        │   └── handler.o
        └── client/
            ├── main.o
            └── request.o
GNUmakefile:
$(if $(MKFWK_MAIN_MAKEFILE),$(eval include $(MKFWK_MAIN_MAKEFILE)),$(eval include mkframework/main.mk))

.PHONY: $(MKFWK_LAST_INCLUDING_DIR)GNUmakefile

# ── Declare both programs ────────────────────────────────────────────────────

server := server
client := client
BIN_PROGRAMS += $(server) $(client)

# ── server ───────────────────────────────────────────────────────────────────

$(server)_ARGS :=
$(server)_CWD  := $(BINDIR)

$(server)_SRCDIR := src/server/
$(call mkfwk_make_check_set_directory_existence,$(server)_SRCDIR)

$(server)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(server)_SRCDIR),'$($(server)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

$(server)_SOURCES := $($(server)_FIND_SOURCES)
$(server)_LDADD   := $($(server)_SOURCES)

$(server)_CPPFLAGS =
$(server)_CFLAGS   =
$(server)_ASFLAGS  =
$(server)_LDFLAGS  =

$(server)_INCLUDEDIR := $($(server)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(server)_INCLUDEDIR)

$(server)_FIND_-I_FLAGS := $(sort \
    $(shell $(FIND) \
        $(if $($(server)_SRCDIR),'$($(server)_INCLUDEDIR)',.) \
        -type f -name '*.h' -print \
      | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
               -e "s?^?-I'?" -e "s?$$?'?" \
               -e "s?^-I''$$?-I'.'?" ;))

$(server)_CPPFLAGS += $($(server)_FIND_-I_FLAGS)

$(server)_LIBDIR := $($(server)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(server)_LIBDIR)

$(server)_FIND_-L_FLAGS := $(sort \
    $(shell $(FIND) \
        $(if $($(server)_SRCDIR),'$($(server)_LIBDIR)',.) \
        -type f \( -name 'lib*.a' -o -name 'lib*.so' \) -print \
      | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
               -e "s?^?-L'?" -e "s?$$?'?" \
               -e "s?^-L''$$?-L'.'?" ;))

$(server)_LDFLAGS += $($(server)_FIND_-L_FLAGS)

# ── client ───────────────────────────────────────────────────────────────────

$(client)_ARGS :=
$(client)_CWD  := $(BINDIR)

$(client)_SRCDIR := src/client/
$(call mkfwk_make_check_set_directory_existence,$(client)_SRCDIR)

$(client)_FIND_SOURCES := $(shell $(FIND) \
    $(if $($(client)_SRCDIR),'$($(client)_SRCDIR)',.) \
    -type f \( \( -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \) \
              -o \( -name '*.y' -o -name '*.l' \) \) \
    -print | $(SED) -e 's?^\./??' ;)

$(client)_SOURCES := $($(client)_FIND_SOURCES)
$(client)_LDADD   := $($(client)_SOURCES)

$(client)_CPPFLAGS =
$(client)_CFLAGS   =
$(client)_ASFLAGS  =
$(client)_LDFLAGS  =

$(client)_INCLUDEDIR := $($(client)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(client)_INCLUDEDIR)

$(client)_FIND_-I_FLAGS := $(sort \
    $(shell $(FIND) \
        $(if $($(client)_SRCDIR),'$($(client)_INCLUDEDIR)',.) \
        -type f -name '*.h' -print \
      | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
               -e "s?^?-I'?" -e "s?$$?'?" \
               -e "s?^-I''$$?-I'.'?" ;))

$(client)_CPPFLAGS += $($(client)_FIND_-I_FLAGS)

$(client)_LIBDIR := $($(client)_SRCDIR)
$(call mkfwk_make_check_set_directory_existence,$(client)_LIBDIR)

$(client)_FIND_-L_FLAGS := $(sort \
    $(shell $(FIND) \
        $(if $($(client)_SRCDIR),'$($(client)_LIBDIR)',.) \
        -type f \( -name 'lib*.a' -o -name 'lib*.so' \) -print \
      | $(SED) -e 's?^\./??' -e 's?[^/]*$$??' -e 's?/$$??' \
               -e "s?^?-L'?" -e "s?$$?'?" \
               -e "s?^-L''$$?-L'.'?" ;))

$(client)_LDFLAGS += $($(client)_FIND_-L_FLAGS)

# ── Footer (must be last) ────────────────────────────────────────────────────

$(eval $(value MKFWK_FOOTER))

Shared vs per-program CFLAGS

Global CFLAGS (set in main.mk or overridden before the MKFWK_FOOTER expansion) apply to every compiled file across all programs. Per-program $(prog)_CFLAGS are appended on top of global CFLAGS for that program only. For example, to enable extra warnings only for the server:
$(server)_CFLAGS = -Wextra
And to disable debug symbols for both programs at once:
make all CC_DEBUG=

Adding custom binary prefixes for test binaries

The BINARY_PREFIXES variable controls which sets of programs the framework processes. By default it contains only BIN. You can add a TEST prefix to place test executables in a separate directory:
# Add this before the MKFWK_FOOTER expansion
BINARY_PREFIXES += TEST
TESTDIR := test-bin/

TEST_PROGRAMS += mytest

mytest_SRCDIR := tests/
$(call mkfwk_make_check_set_directory_existence,mytest_SRCDIR)
mytest_FIND_SOURCES := $(shell $(FIND) 'tests/' \
    -type f -name '*.c' ! -name '*.tab.c' ! -name '*.lex.yy.c' \
    -print | $(SED) -e 's?^\./??' ;)
mytest_SOURCES := $(mytest_FIND_SOURCES)
mytest_LDADD   := $(mytest_SOURCES)
mytest_ARGS    :=
mytest_CWD     := $(TESTDIR)
The test binary will be placed in test-bin/ and will have its own run-mytest, gdb-mytest, and valgrind-*-mytest targets.

Next steps

Libraries

Add static or shared libraries to the same GNUmakefile.

Running and debugging

All run, GDB, and Valgrind targets and their options.

Bison and Flex project

Add parser and scanner generation to any program in the list.

Build pipeline

Understand how the framework processes sources through to the final binary.

Build docs developers (and LLMs) love