Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the makefile insist on compiling things it should not?

Tags:

c

makefile

So I run make lex and it generates me the lex.yy.c file, all good

then I run make scanner, which takes a sourcefile called scanner.c into compilation, it should simply run cc lex.yy.c scanner.c -o scanner, but instead it does this:

lex  -t scanner.l > scanner.c
cc lex.yy.c scanner.c -o scanner 

Why does it decide to run lex -t scanner.l and output it to scanner.c effectively overwritting my code? No damned idea, and it's driving me crazy.

my makefile:

scanner: scanner.h scanner.c lex.yy.c
    cc lex.yy.c scanner.c -o scanner 

lex: scanner.l
    lex scanner.l > lex.yy.c

clean:
    rm lex.yy.c
    rm scanner

What's going wrong?

like image 461
Jose V Avatar asked Sep 17 '17 07:09

Jose V


2 Answers

Why does it decide to run lex -t scanner.l and output it to scanner.c effectively overwritting my code?

Whenever this happens you have in your build directory a scanner.c and a scanner.l that is more recent than than the scanner.c

When you run make scanner, the recipe:

scanner: scanner.h scanner.c lex.yy.c
    cc lex.yy.c scanner.c -o scanner

requires that its prerequisite scanner.c shall be made uptodate. You have provided no recipe that does that, so make falls back on its database of builtin recipes.

Examine those builtin recipes by running make -p and you'll find:

%.c: %.l
#  recipe to execute (built-in):
    @$(RM) $@ 
     $(LEX.l) $< > $@

This builtin recipe will make a file.c from the matching file.l by running:

rm file.c   # Not echoed
lex  -t file.l > file.c

Make finds that the pattern rule for this recipe - %.c: %.l - is satisfied by scanner.c, because scanner.l exists and more recent that than scanner.c. So it uses this recipe to make scanner.c uptodate:

lex  -t scanner.l > scanner.c

thereby clobbering your scanner.c.

If you do not want make ever to apply this builtin recipe, you can explicitly cancel it by writing just the rule:

%.c: %.l

in your makefile without any recipe.

You can also disable all builtin recipes by passing --no-builtin-rules on the make commandline.

However, whenever your expectations about the behaviour of a makefile are sabotaged by the builtin recipes it is strong indication that your expectations are not well-informed as to the usual way of making output files from input files with the tools that your makefile invokes. Make's Catalogue of Built-In Rules:

%.<target-type>: %.<prereq-type>
    <command>
    ...

embody the canonical way of making a <target-type> file from a <prereq-type> file with make, so that you don't have to keep writing this recipe yourself in your makefiles. For example C and C++ programmers who are competent with GNU make do not write recipes to make .o files from .c files or .cpp files, except in corner cases, because they are know that the way make will do it automatically is normally the way they want.

Make's builtin recipe for the %.c: %.l rule expresses the canonical way of making file.c given a file.l. So ask yourself: If you don't want scanner.c to be made like that from scanner.l, and if instead you want lex.yy.c to be made from scanner.l, is it necessary or useful for the file you have called scanner.l to be called that, when you also have a quite independent source file called scanner.c?

Suppose you took advantage of make's builtin recipes as in this toy example:

lexer.l

%{
#include <stdio.h>
%}
%%
[ \t] ;
[0-9]+\.[0-9]+ { printf("Found a floating-point number: [%s]\n",yytext); }
[0-9]+  { printf("Found an integer: [%s]\n",yytext); }
[a-zA-Z0-9]+ { printf("Found a string: [%s]\n",yytext); }
%%

scanner.c

#include "scanner.h"

int main(void) {
    yylex();
    return 0;
}

scanner.h

#ifndef SCANNER_H
#define SCANNER_H

extern int yylex(void);

#endif

Then your makefile could be just:

Makefile

SRCS := scanner.c lexer.c
OBJS := $(SRCS:.c=.o)
LDLIBS := -lfl

.PHONY: all clean

all: scanner

scanner: $(OBJS)
    $(LINK.o) -o $@ $^ $(LDLIBS)

scanner.o: scanner.h

clean:
    rm -f scanner *.o

which runs like:

$ make
cc    -c -o scanner.o scanner.c
lex  -t lexer.l > lexer.c
cc    -c -o lexer.o lexer.c
cc   -o scanner scanner.o lexer.o -lfl
rm lexer.c

Note that make runs all these commands:

    cc    -c -o scanner.o scanner.c
    lex  -t lexer.l > lexer.c
    cc    -c -o lexer.o lexer.c

to make lexer.c, lexer.o and scanner.o without you having written any recipes that tell it how to do so. It also automatically notices that lexer.c is an intermediate file - a generated file that only needs to exist to get from lexer.l to lexer.o - so it deletes it at the end:

rm lexer.c

without being told to.

And this scanner runs like:

$ ./scanner 
hello
Found a string: [hello]

42
Found an integer: [42]

42.42
Found a floating-point number: [42.42]

^C
like image 82
Mike Kinghan Avatar answered Oct 29 '22 00:10

Mike Kinghan


The make you are using most certainly defines default suffixes, defining implicit rules which conflicts with the ones you define.

See for instance this howto, which explains suffix rules.

At the beginning of your makefiles, you can add a line:

.SUFFIXES:

to tell make you don't want default suffix rules.

like image 40
tonio Avatar answered Oct 28 '22 22:10

tonio