-2

I'm trying to learn how Makefile works, so I decided to try it with a very simple code. This is what I wrote:

/* justify.c */

#include <stdio.h>
#include "line.h"
#include "word.h"

int main(void) {
    printf("I'm in the main.\n");
    read_word();
    write_line();
}
/* line.h */

void write_line(void);
/* line.c */

#include <stdio.h>
#include "line.h"

void write_line(void) {
    printf("write_line\n");
}
/* word.h */

void read_word(void);
/* word.c */

#include <stdio.h>
#include "word.h"

void read_word(void) {
    printf("read_word\n");
}

now ... if I do everything from the terminal it works:

> gcc -c justify.c
> gcc -c line.c
> gcc -c word.c
> gcc -o justify justify.c line.c word.c

but if I try to do everything with a Makefile it gives me an error:

# Makefile 

justify: justify.o line.o word.o
    gcc -o justify.o line.o word.o

justify.o: justify.c line.h word.h
    gcc -c justify.c

line.o: line.c line.h
    gcc -c line.c

word.o: word.c word.h
    gcc -c word.c
> make justify

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Roberto Rocco
  • 450
  • 2
  • 11
  • 1
    You should add include guards to the `*.h` files and remove them from the dependencies in the makefile. Not that it is related to your problem though – Eugene Sh. Nov 06 '19 at 18:27
  • 5
    `gcc -o justify.o line.o word.o` -> `gcc -o justify justify.o line.o word.o` – tkausl Nov 06 '19 at 18:27
  • 2
    check the `gcc -o` line. should that not be `gcc -o justify justify.o line.o word.o` – mpez0 Nov 06 '19 at 18:27
  • Thanks, everyone, including you Eugene Sh. The 'include guards' are very interesting. – Roberto Rocco Nov 06 '19 at 19:41
  • The issue that @tkausl pointed out, while valid, would not explain the message you're seeing, so something else is going on.. It looks like line.o was compiled for another architecture. Do an `objdump -f line.o`. Then delete the .o file, and recompile from the command line and check again. If the makefile is producing different output than the command line, then try running `which gcc` from both your shell and a makefile recipe to see if there's an issue with the path. – HardcoreHenry Nov 06 '19 at 19:43
  • @HardcoreHenry `would not explain the message you're seeing` It does since neither `line.o` nor `word.o` contain the main function. – tkausl Nov 06 '19 at 19:49
  • Ahh yes, I stand corrected. – HardcoreHenry Nov 06 '19 at 19:55
  • @tkausl Yours would be the solution to my problem, what should I do to confirm that it is the correct answer? – Roberto Rocco Nov 06 '19 at 22:22

3 Answers3

1

No pun intended, but you are making make a bit harder than it needs to be. In your case with the source and headers all in the same directory, it is quite simple to use wildcards to allow make to handle the rest. For example with three simple variable declartions in your Makefile, you can tell make what your sources are, your includes and how to generate an object file for each of the source files that isn't the application name.

For starters, specify your application name:

# application name
APPNAME := justify

If needed, set your compiler variables, e.g.

# compiler
CC      := gcc
CCLD    := $(CC)

Now make knows your application name is held in APPNAME which you access like any other variable in a Makefile as $(APPNAME).

Now just use wildcard to collect all sources and includes in variables as well, and let make associate the object file output:

# source/include/object variables
SOURCES := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS := $(SOURCES:%.c=%.o)

Set your compiler/linker/library flags:

# compiler and linker flags
CFLAGS  := -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast
LDFLAGS :=
# libraries
LIBS    :=

Now create your default target for make (note: there must be a tab-character '\t' in front of each rule):

all:    $(OBJECTS)
    $(CCLD) -o $(APPNAME) $(OBJECTS) $(CFLAGS) $(LDFLAGS) $(LIBS)

A rule to compile all sources to objects:

$(OBJECTS): %.o : %.c
    $(CC) $(CFLAGS) -c -o $@ $<

(see: What do the makefile symbols $@ and $< mean? for explanation of the automatic variables used)

And finally a target for clean:

clean:
    rm -rf $(APPNAME) *.o

A complete example for your files would be:

# application name
APPNAME := justify
# compiler
CC      := gcc
CCLD    := $(CC)
# compiler and linker flags
CFLAGS  := -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast
LDFLAGS :=
# libraries
LIBS    :=
# source/include/object variables
SOURCES := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS := $(SOURCES:%.c=%.o)

# target for all
all:    $(OBJECTS)
    $(CCLD) -o $(APPNAME) $(OBJECTS) $(CFLAGS) $(LDFLAGS) $(LIBS)
# strip only if -DDEBUG not set
ifneq ($(debug),-DDEBUG)
    strip -s $(APPNAME)
endif

$(OBJECTS): %.o : %.c
    $(CC) $(CFLAGS) -c -o $@ $<

clean:
    rm -rf $(APPNAME) *.o

(note: a rule to strip the executable was also added to the all: target)

Example Build

With your Makefile source and include files in a common directory, e.g.

$ ll
total 24
-rw-r--r-- 1 david david 668 Nov  6 12:38 Makefile
-rw-r--r-- 1 david david 161 Nov  6 12:31 justify.c
-rw-r--r-- 1 david david 106 Nov  6 12:32 line.c
-rw-r--r-- 1 david david  37 Nov  6 12:31 line.h
-rw-r--r-- 1 david david 104 Nov  6 12:32 word.c
-rw-r--r-- 1 david david  36 Nov  6 12:32 word.h

Just type make to have your application built:

$ make
gcc -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast -c -o word.o word.c
gcc -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast -c -o line.o line.c
gcc -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast -c -o justify.o justify.c
gcc -o justify word.o line.o justify.o -Wall -Wextra -pedantic -finline-functions -std=c11 -Wshadow -Ofast
strip -s justify

No errors, you can check all files were created as expected:

$ ll
total 44
-rw-r--r-- 1 david david  668 Nov  6 12:38 Makefile
-rwxr-xr-x 1 david david 6312 Nov  6 13:01 justify
-rw-r--r-- 1 david david  161 Nov  6 12:31 justify.c
-rw-r--r-- 1 david david 1760 Nov  6 13:01 justify.o
-rw-r--r-- 1 david david  106 Nov  6 12:32 line.c
-rw-r--r-- 1 david david   37 Nov  6 12:31 line.h
-rw-r--r-- 1 david david 1496 Nov  6 13:01 line.o
-rw-r--r-- 1 david david  104 Nov  6 12:32 word.c
-rw-r--r-- 1 david david   36 Nov  6 12:32 word.h
-rw-r--r-- 1 david david 1496 Nov  6 13:01 word.o

Test your executable:

$ ./justifiy
I'm in the main.
read_word
write_line

Lastly, clean your build directory with make clean, e.g.

$ make clean

And confirm all build files are removed.

That's about the easiest way to go about writing minimal make files. You can list each object individually and the required includes to support them, but why? the automatic variables will take care of that for you. There is much, much more you can do with Makefiles, but for getting started, this will make your life easier.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
1

You can heavily simplify the Makefile you have. Make provides lots of helpful variables.

$@ - target name
$< - first prequisite
$^ - all prequsites
justify: justify.o line.o word.o
        gcc -o $@ $^    # this one will turn into
                        # gcc -o justify justify.o line.o word.o 

justify.o: justify.c
        gcc -c $<       # this one will turn into
                        # gcc -c justify.c

line.o: line.c
        gcc -c $<

word.o: word.c
        gcc -c $<

it's also a good idea to add clean

clean:
        -rm *.o justify
-rm - minus sign at the beginning will not produce error if command fails;   
      useful in case you expect that something might be missing
Oo.oO
  • 12,464
  • 3
  • 23
  • 45
0

There is bug in your make file. The commands for the make target justify: are missing the output file name. It should be like this.

gcc -o justify justify.o line.o word.o

with your current command line the gcc will try to output justify.o by linking line.o and word.o and the it does not find _main which is not defined in line.o and word.o

The make command already knows how to convert a .c file to .o so you do not need to tell this to make again in the make file. Keeping this in mind, the following make file is enough for your test case.

# Makefile 
justify: justify.o line.o word.o
    gcc -o justify justify.o line.o word.o

You can further simplify this by using make's built-in automatic variables. $@ and $^. The $@ variable contains the name of the target of rule. and $^ contains the name of all the prerequisites of the rule. So using these two variables your make file will be.

justify: justify.o line.o word.o
        gcc -o $@ $^
AliA
  • 690
  • 6
  • 8