Question

Fixing "missing separator" in a Makefile (GNU Make)

cmakefile

Question

I have the following Makefile:

Copyall: ll

ll: ll.c
  gcc -c -Wall -Werror -O2 c.c ll.c -o ll $@ $<

clean:
  \rm -fr ll

When I run make or make clean, I get this error:

makefile:4: *** missing separator.  Stop.

What does this error mean, and how can I fix the Makefile correctly?

Short Answer

By the end of this page, you will understand what the GNU Make *** missing separator error means, why it usually happens when indentation is wrong, and how to write valid Makefile rules using targets, prerequisites, and tab-indented recipe lines.

Concept

make reads a Makefile using very strict syntax.

A basic rule looks like this:

target: prerequisites
	command

The important detail is this: each command line inside a rule must begin with a tab character unless you intentionally change the recipe prefix.

The error:

*** missing separator

usually means make expected a tab-indented command line, but found spaces or some other invalid text instead.

In your file, the command lines under ll: and clean: are indented with spaces instead of a tab. GNU Make treats that as invalid syntax.

This matters because Makefiles are not like many programming languages where spaces and tabs are interchangeable. In a Makefile recipe, indentation has meaning.

There are also a couple of other issues in the example:

  • -02 should almost certainly be -O2 with a capital letter O
  • gcc -c ... -o ll is inconsistent because -c compiles without linking and normally outputs an object file like ll.o
  • c.c may be a typo if your source file is actually ll.c
  • $@ and $< are automatic variables, but they should be used appropriately

So the main syntax error is indentation, but it is also a good chance to clean up the build rule itself.

Mental Model

Think of a Makefile as a checklist with a very strict format:

  • The target is the thing you want to build.
  • The prerequisites are the files it depends on.
  • The recipe is the set of shell commands used to build it.

A simple way to picture it:

  • ll: = the name of the job
  • ll.c = what the job needs first
  • tab + gcc ... = the actual instruction to run

If you write the instruction with spaces instead of a tab, make does not recognize it as a command. It is like filling out a form where one field must start in a specific column. If it does not, the form is rejected.

Take Quiz

Syntax and Examples

The core Makefile syntax is:

target: prerequisite1 prerequisite2
	command 1
	command 2

Minimal correct example

ll: ll.c
	gcc -Wall -Werror -O2 ll.c -o ll

clean:
	rm -f ll

What this does

  • ll is built from ll.c
  • The command compiles and links the program into an executable named ll
  • clean removes the generated executable

Better version with variables

CC = gcc
CFLAGS = -Wall -Werror -O2

ll: ll.c
	$(CC) $(CFLAGS) ll.c -o ll

clean:
	rm -f ll

This version is easier to maintain because compiler options are stored in variables.

If you want a default target

all: ll

ll: ll.c
	gcc -Wall -Werror -O2 ll.c -o ll

clean:
	rm -f ll

Now running make builds all, which depends on ll.

Step by Step Execution

Consider this Makefile:

all: ll

ll: ll.c
	gcc -Wall -Werror -O2 ll.c -o ll

clean:
	rm -f ll

Now run:

make

Step by step

  1. make looks for the first target.
  2. The first target is all.
  3. all depends on ll, so make checks whether ll exists and whether it is up to date.
  4. To build ll, make sees that it depends on ll.c.
  5. If ll does not exist, or ll.c is newer than ll, make runs:
gcc -Wall -Werror -O2 ll.c -o ll
  1. The executable ll is created.
  2. Since all has no separate recipe, make finishes.

Now run:

make clean

What happens

  1. make looks for the clean target.
  2. It runs the command under clean:
rm -f ll
  1. The executable is removed.

Why the original file failed

In the original file, the command line looked visually correct, but it started with spaces:

ll: ll.c
  gcc -Wall -Werror -O2 ll.c -o ll

make expected a tab before gcc, did not find one, and reported:

*** missing separator

Real World Use Cases

Makefiles are commonly used for:

  • Compiling C and C++ programs
  • Running test suites
  • Cleaning generated files
  • Building documentation
  • Automating repetitive shell commands

Practical examples

Build an app

app: main.c utils.c
	gcc -Wall main.c utils.c -o app

Run tests

test:
	pytest

Clean generated files

clean:
	rm -f app *.o

Build and package

release:
	gcc -O2 main.c -o app
	tar -czf app.tar.gz app

Even in modern projects, make is still useful as a simple task runner.

Take Quiz

Real Codebase Usage

In real projects, developers usually write Makefiles with a few common patterns.

Variables for compiler settings

CC = gcc
CFLAGS = -Wall -Werror -O2
TARGET = ll

$(TARGET): ll.c
	$(CC) $(CFLAGS) ll.c -o $(TARGET)

This avoids repeating the same flags.

.PHONY for non-file targets

Targets like clean do not usually create a file named clean. Mark them as phony:

.PHONY: all clean

all: ll

clean:
	rm -f ll

This prevents conflicts if a real file named clean exists.

Separate compile and link steps

For larger projects:

CC = gcc
CFLAGS = -Wall -Werror -O2

ll: ll.o
	$(CC) ll.o -o ll

ll.o: ll.c
	$(CC) $(CFLAGS) -c ll.c -o ll.o

This is more scalable because only changed source files need recompilation.

Common maintenance targets

.PHONY: clean run

run: ll
	./ll

clean:
	rm -f ll ll.o

These patterns make Makefiles easier to read and maintain.

Common Mistakes

1. Using spaces instead of a tab

Broken:

ll: ll.c
  gcc -Wall ll.c -o ll

Fixed:

ll: ll.c
	gcc -Wall ll.c -o ll

This is the most common cause of missing separator.

2. Writing -02 instead of -O2

Broken:

gcc -02 ll.c -o ll

Fixed:

gcc -O2 ll.c -o ll

The optimization flag uses a capital letter O, not zero.

3. Mixing -c with executable output

Broken:

gcc -c ll.c -o ll

-c means compile only, producing an object file, usually ll.o.

Fixed compile-only version:

gcc -c ll.c -o ll.o

Fixed compile-and-link version:

gcc ll.c -o ll

4. Misusing automatic variables

Broken:

ll: ll.c
	gcc ll.c -o ll $@ $<

This is not useful here because ll and ll.c are already written explicitly.

Better:

ll: ll.c
	gcc $< -o $@

Here:

  • $@ = target name (ll)
  • $< = first prerequisite (ll.c)

5. Forgetting .PHONY for clean

If a file named clean exists, make clean may not run as expected.

Better:

.PHONY: clean

clean:
	rm -f ll

Comparisons

Recipe line vs target line

PartExamplePurpose
Target linell: ll.cDeclares what to build and what it depends on
Recipe line\tgcc ll.c -o llCommand that builds the target

Tabs vs spaces in Makefiles

IndentationResult
Tab before recipe commandValid
Spaces before recipe commandUsually causes missing separator

Compile only vs compile and link

CommandMeaningOutput
gcc -c ll.c -o ll.oCompile onlyObject file
gcc ll.c -o llCompile and linkExecutable

Explicit command vs automatic variables

StyleExampleWhen useful
Explicitgcc ll.c -o llSimple beginner examples
Automatic variablesgcc $< -o $@Reusable, pattern-based rules

rm -f vs \rm -fr

CommandMeaningSafer choice here?
rm -f llRemove file if it existsYes
\rm -fr llForce remove recursivelyUsually unnecessary for one file
Take Quiz

Cheat Sheet

Basic Makefile rule

target: prerequisites
	command

Important rule

  • Recipe commands must start with a tab, not spaces.

Common fix for missing separator

Replace this:

ll: ll.c
  gcc ll.c -o ll

With this:

ll: ll.c
	gcc ll.c -o ll

Simple working example

.PHONY: all clean

all: ll

ll: ll.c
	gcc -Wall -Werror -O2 ll.c -o ll

clean:
	rm -f ll

Useful automatic variables

  • $@ = target name
  • $< = first prerequisite
  • $^ = all prerequisites

Example:

ll: ll.c
	gcc $< -o $@

Common flags

  • -Wall = enable many warnings
  • -Werror = treat warnings as errors
  • -O2 = optimization level 2
  • -c = compile only, do not link

Good habits

  • Use .PHONY for clean, all, run
  • Use variables like CC and CFLAGS
  • Use rm -f for cleaning generated files
  • Keep target names and file names consistent

FAQ

Why does Make say missing separator?

Usually because a recipe command starts with spaces instead of a tab.

Can I use spaces instead of tabs in a Makefile?

Not for normal recipe lines. GNU Make expects a tab unless you intentionally configure a different recipe prefix.

Why does make clean fail even though clean is simple?

Because make must parse the whole file first. A syntax error anywhere in the Makefile prevents all targets from running.

Should I use -c when building an executable?

No. -c is for compile-only mode and usually produces an object file like .o. To create an executable, compile and link without -c.

What is the difference between $@ and $<?

$@ is the target name. $< is the first prerequisite.

Why should I add .PHONY: clean?

It tells make that clean is a command target, not a file to build.

What is the corrected version of this Makefile?

A simple corrected version is:

.PHONY: all clean

all: ll

ll: ll.c
	gcc -Wall -Werror -O2 ll.c -o ll

clean:
	rm -f ll
Take Quiz

Mini Project

Description

Create a small Makefile for a C program that can build the executable and remove generated files. This project helps you practice correct Makefile syntax, especially target definitions and tab-indented recipe lines.

Goal

Write a working Makefile that builds a C program named hello from hello.c and supports a clean command.

Requirements

  • Create a default target that builds hello.
  • Compile hello.c into an executable named hello.
  • Add a clean target that removes the executable.
  • Use tab indentation for all recipe commands.
  • Mark non-file targets appropriately.
Take Quiz

Keep learning

Related questions

View more