Почему makefile запрашивает папку для сгенерированного файла

#makefile

#makefile

Вопрос:

Я написал makefile для компиляции для сгенерированного файла

 %.o: %.cc
    g   -c $< -o $@

default: gen main.o

gen:
    touch main.cc
  

И получил

 $ make default
touch main.cc
make: *** No rule to make target 'main.o', needed by 'default'.  Stop.
  

Но если я добавлю папку для сгенерированных файлов .cc и .o, это сработает

 obj/%.o: src/%.cc
    g   -c $< -o $@

default: gen obj/main.o

gen:
    touch src/main.cc
  

Почему запрашиваются папки obj и src

Ответ №1:

Это не так. Такое поведение связано с make оптимизацией и тем, что вы Makefile неискренни в том, что он делает.

При make чтении каталога он хэширует все содержимое каталога и выполняет будущие поиски файлов в этом хэше. При make первом запуске main.cc файла нет, поэтому хэш каталога не содержит записи для main.cc . Поскольку ваш Makefile генерирует этот файл, но не объявляет его, make не знает, что существует цель, которая генерирует main.cc файл, и когда он ищет в хэше, файла там нет. Поэтому ваше неявное правило отклоняется как невозможное:

 $ make -dr default
...
Considering target file 'default'.
 File 'default' does not exist.
 Looking for an implicit rule for 'default'.
 No implicit rule found for 'default'.
  Considering target file 'gen'.
   File 'gen' does not exist.
   Finished prerequisites of target file 'gen'.
  Must remake target 'gen'.
touch main.cc
Putting child 0x55de3daaa6a0 (gen) PID 6075 on the chain.
Live child 0x55de3daaa6a0 (gen) PID 6075
Reaping winning child 0x55de3daaa6a0 PID 6075
Removing child 0x55de3daaa6a0 PID 6075 from chain.
  Successfully remade target file 'gen'.
  Considering target file 'main.o'.
   File 'main.o' does not exist.
   Looking for an implicit rule for 'main.o'.
   Trying pattern rule with stem 'main'.
   Trying implicit prerequisite 'main.cc'.
   Trying pattern rule with stem 'main'.
   Trying implicit prerequisite 'main.cc'.
   Looking for a rule with intermediate file 'main.cc'.
    Avoiding implicit rule recursion.
   No implicit rule found for 'main.o'.
   Finished prerequisites of target file 'main.o'.
  Must remake target 'main.o'.
make: *** No rule to make target 'main.o', needed by 'default'.  Stop.
  

Если вы strace выполните этот вызов, вы заметите две вещи: а) getdents64 вызов, который считывает содержимое каталога (используется для поиска и хэширования) и б) отсутствие stat for main.cc вообще, что означает, что на диске нет проверки на наличие файла, поскольку его наличие было разрешено из кэша:

 $ strace make -r default
...
stat(".", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
getdents64(3, /* 3 entries */, 32768)   = 80
getdents64(3, /* 0 entries */, 32768)   = 0
close(3)                                = 0
...
stat("gen", 0x7fff4f66a3d0)             = -1 ENOENT (No such file or directory)
stat("main.o", 0x7fff4f66a460)          = -1 ENOENT (No such file or directory)
... <-- locale-related calls for error message
write(2, "make: *** No rule to make target"..., 71make: *** No rule to make target 'main.o', needed by 'default'.  Stop.
  

Теперь, если вы повторите ту же команду, она будет успешной, потому main.cc что файл уже создан и доступен, когда каталог кэшируется:

 $ make -dr default
...
Considering target file 'default'.
 File 'default' does not exist.
 Looking for an implicit rule for 'default'.
 No implicit rule found for 'default'.
  Considering target file 'gen'.
   File 'gen' does not exist.
   Finished prerequisites of target file 'gen'.
  Must remake target 'gen'.
touch main.cc
Putting child 0x555646acc6a0 (gen) PID 6079 on the chain.
Live child 0x555646acc6a0 (gen) PID 6079
Reaping winning child 0x555646acc6a0 PID 6079
Removing child 0x555646acc6a0 PID 6079 from chain.
  Successfully remade target file 'gen'.
  Considering target file 'main.o'.
   File 'main.o' does not exist.
   Looking for an implicit rule for 'main.o'.
   Trying pattern rule with stem 'main'.
   Trying implicit prerequisite 'main.cc'.
   Found an implicit rule for 'main.o'.
    Considering target file 'main.cc'.
     Looking for an implicit rule for 'main.cc'.
     No implicit rule found for 'main.cc'.
     Finished prerequisites of target file 'main.cc'.
    No need to remake target 'main.cc'.
   Finished prerequisites of target file 'main.o'.
  Must remake target 'main.o'.
g   -c main.cc -o main.o
Putting child 0x555646ad0690 (main.o) PID 6080 on the chain.
Live child 0x555646ad0690 (main.o) PID 6080
Reaping winning child 0x555646ad0690 PID 6080
Removing child 0x555646ad0690 PID 6080 from chain.
  Successfully remade target file 'main.o'.
 Finished prerequisites of target file 'default'.
Must remake target 'default'.
Successfully remade target file 'default'.
  

Если вы играете по правилам и пишете Makefile правильно, объявление создаваемых файлов make будет выполняться должным образом:

 $ cat Makefile
%.o: %.cc
        g   -c $< -o $@

default: main.o

main.cc:
        touch $@

$ make default
touch main.cc
g   -c main.cc -o main.o