How can I make a pattern rule dependency optional in a Makefile? - makefile

I would like make to reference the timestamp of a dependency if and only if the file already exists. I have a pattern rule like this:
%.pdf: %.sil
sile $< -o $#
This works great in normal situations, but the .sil file makes an external reference to a lua file of the same name if it exists. How do I make make aware of this so it checks the timestamps and regenerates the PDF if the lua file is newer but ignores the dependency if the file doesn't exist at all?
This:
%.pdf: %.sil %.lua
sile $< -o $#
…only works for cases where the file exists and causes an error if it doesn't.

With a sufficiently new version of GNU make you can use:
.SECONDEXPANSION:
%.pdf: %.sil $$(wildcard $$*.lua)
sile $< -o $#
See the manual section for SECONDEXPANSION targets and the wildcard function.

Related

Make removes files as intermediate

When trying to organize the compilation output into a build directory, make keeps removing object files. The Makefile is:
MPI_INSTALLATION=/home/gkaf/Software/MPI
IDIR=$(MPI_INSTALLATION)/include
LDIR=$(MPI_INSTALLATION)/lib
CC=gcc
CFLAGS=-I$(IDIR)
LDFLAGS=-L$(LDIR) -Wl,-rpath=$(LDIR)
BUILD_DIR=build
OBJ_DIR=$(BUILD_DIR)/obj
BIN_DIR=$(BUILD_DIR)/bin
SRC_DIR=src
LIBS=-lmpi
.PHONY: all
all: test-mpi
.PHONY: test-mpi
test-mpi: prepare $(BIN_DIR)/test-mpi
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) -c -o $# $< $(CFLAGS)
$(BIN_DIR)/%: $(OBJ_DIR)/%.o
$(CC) -o $# $^ $(LDFLAGS) $(LIBS)
prepare: $(BUILD_DIR) $(OBJ_DIR) $(BIN_DIR)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(OBJ_DIR):
mkdir -p $(OBJ_DIR)
$(BIN_DIR):
mkdir -p $(BIN_DIR)
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
The object file build/obj/test-mpi.o generated during the compilation is deleted after the executable build/bin/test-mpi is created.
I believe that make treats build/obj/test-mpi.o as an intermediate file. However, I was expecting that build/obj/test-mpi.o would not be treated as an intermediate file since it appears explicitly in the target $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c and gnu make documentation states that "ordinarily, a file cannot be intermediate if it is mentioned in the makefile as a target or prerequisite".
This behavior has been reported in a similar issue, but I believe that in both cases the files should not be treated as intermediate since they appear in a target. Am I missing something?
I believe that make treats build/obj/test-mpi.o as an intermediate file.
Yes, that looks right.
However, I was expecting that build/obj/test-mpi.o would not be treated as an intermediate file since it appears explicitly in the target $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c [...]
Given that $(OBJ_DIR) expands to build/obj, the pattern $(OBJ_DIR)/%.o matches build/obj/test-mpi.o. That's the opposite of build/obj/test-mpi.o appearing explicitly.
Even so, you have read the GNU make documentation correctly: make would not consider build/obj/test-mpi.o to be an intermediate file if it was mentioned as a target or prerequisite of some other rule. But it isn't. If make builds that file at all, it is entirely make's idea, notwithstanding the fact that you set the stage for it to come to that decision. This is exactly what it means to be an intermediate file.
Am I missing something?
Apparently you are missing what it means for a file to be "mentioned in" a makefile, as the GNU docs put it. It means that the file's name appears literally in the makefile text, after macro expansion, as a target or prerequisite of a rule. Example:
$(BIN_DIR)/test-mpi: $(OBJ_DIR)/test-mpi.o
or
$(OBJ_DIR)/test-mpi.o: $(SRC_DIR)/test-mpi.c
Matching a target or prerequisite pattern of an implicit ("pattern") rule does not suffice. In fact, it is exactly files that are generated as intermediates in chains of implicit rules that make aims to remove. Implicit rules defined in the makefile are not distinguished from make's built-in implicit rules in this regard.
However, although files such as the one you asked about are definitely intermediate files as GNU make defines that term, make has an additional capability here that might serve your purposes. If you want to use a pattern to specify intermediate targets that you want to preserve, then you can do so by designating the pattern as a prerequisite of the special target .PRECIOUS, like so:
.PRECIOUS: $(OBJ_DIR)/%.o
Intermediate files matching such a pattern will be spared from the automatic deletion to which they otherwise they would be subject.

GNU make generate assembly first, them compile them to .o and link

SOURCE=a.c b.c c.c
ASM=$(patsubst %.c,%.s, $(SOURCE))
all:%.o
gcc -o test $^
$(ASM):%.c
gcc -S -o $# $<
%.o:%.s
gcc -c -o$# $<
I want to generate assembly code (.s) first, then compile the assembly code to object (.o), then link them.
But it seems above makefile code does not work. What is the correct code?
When asking questions, does not work is never very useful... if it worked you probably wouldn't be asking a question! :-) Instead you should always show the command you ran and the output you received (or at least the failing part of the output if it's long). Please cut and paste the actual text rather than paraphrasing messages. Also, including the version of the make program you're using (make --version) and the platform you're running on is often helpful.
Luckily this time we can figure out the problem without this information:
This:
$(ASM):%.c
gcc -S -o $# $<
where ASM is a.s b.s c.s, is not a pattern rule because the targets don't contain a pattern character %. That means the prerequisite %.c is not treated as a pattern, but as an actual file name, literally %.c which obviously doesn't exist.
Similarly, this:
all: %.o
has the same problem: all is a target, so this depends on the literal file named %.o which doesn't exist, and can't be created.
Also as a general rule every recipe that creates a target must create the actual target you told make it would, so this all rule is wrong because the target name is all but the recipe creates the target test.
Finally, it's a very bad idea to name your program test because test is a common UNIX program and a shell built-in, so if you run test it won't do the right thing (if you run ./test it will work).
You want to have all depend on the program you want to build, say mytest, and mytest should depend on the actual .o files:
all: mytest
mytest: $(SOURCE:.c=.o)
gcc -o $# $^
Next, you need to define a pattern rule that knows how to create an assembly file from a source file:
%.s : %.c
gcc -S -o $# $<
That, along with your other pattern rules, is all you need: make will figure it all out from that.
Finally, make has a built-in rule that tells it how to build object files directly from source files. It's best to get rid of this to force make to use your rules; add this to your makefile to delete it:
%.o : %.c

makefile: performing include to a .mak file after certain action on it

I have a large project I'm working on, in which I want to perform include to some .mak file, but only after I make change to this file content via a command inside the original makefile. Since it's a large project it will be hard to write code, so I will give this ridiculous example instead:
I have some small C project that all it's C and header files are in the same directory, and I need to write a makefile. I'm not allowed to use clean rule in the makefile I write, but I have a file named file.mak that I can include in my makefile. Content of file.mak:
.PHONY: clean
cleam:
$(RM) $(objs) test
The problem here is that the rule is cleam and not clean. I'm also not allowed to change manually file.mak , but I'm allowed to do this with a command inside the original makefile. This can be done easily by:
sed -i 's/cleam/clean/g' file.mak
So I thought of writing the makefile like this:
CC = gcc
srcs = $(wildcard ./*.c)
objs = $(srcs:.c=.o)
test: $(objs) change_file include_file
$(CC) $^ -o $#
%.o: %.c
$(CC) -c $< -o $#
change_file:
$(shell sed -i 's/cleam/clean/g' file.mak)
include_file: change_file
include file.mak
But I get the following error:
include: Command not found
So I understand that there is a problem of using include inside a rule, so is there a way to achieve what I want?
(GNU) make has a feature Remaking Makefiles that can be used for scenarios like this, but your approach is wrong. include is a directive and can't be used in a recipe.
Instead, when you include a file, make first checks for rules creating this exact file and executes them. As in your case, the file you want to include already exists, you have to make this rule .PHONY to force its execution. It would look like this:
.PHONY: file.mak
file.mak:
sed -i 's/cleam/clean/g' file.mak
include file.mak
As a more robust alternative (without the need for a phony rule), consider creating a fixed version (copy) and include this:
file_fixed.mak: file.mak
sed -e 's/cleam/clean/g' <file.mak >file_fixed.mak
include file_fixed.mak

Object file directory per compiler option combinations

I was reading gnu make section 10.5.4 "How patterns match" and it does not sound like I can do what I want.
I want to setup a directory structure where my source code is in one directory, and there are sub-directories to hold object files.
One sub-directory for each build configuration.
So I might have these files
a.c
debug/a.o # compiled with -g
release/a.o # compiled with -O
So I would like to make rules like this
debug/%.o : %.c
gcc -c -g %.c -o $#
release/%.o : %.c
gcc -c -O %.c -o $#
But section 10.5.4 tells me a match on "debug/a.o" will make the stem be "debug/a" so gnu make
will look for the source file at "debug/a.c" which is not what I want.
Is there a way to get GNU make to help me ?
Your makefile will work as written.
From that section of the manual:
When the target pattern does not contain a slash (and it usually does
not), directory names in the file names are removed from the file name
before it is compared with the target prefix and suffix. After the
comparison of the file name to the target pattern, the directory
names, along with the slash that ends them, are added on to the
prerequisite file names generated from the pattern rule's prerequisite
patterns... [bold added]
Your target patterns do contain slashes.
Try it if you don't believe me.
EDIT:
Correction: in the commands you should use $< rather than %.c.
CC=gcc
DEBUGFLAGS=-g
RELEASEFLAGS=-O
debug/%.o : %.c
$(CC) $(DEBUGFLAGS) -c $< -o $#
release/%.o : %.c
$(CC) $(RELEASEFLAGS) -c $< -o $#

What do $< and $# mean in makefiles?

I have a.csv,b.csv, ... in a my docs/csv directory, I need convert each of this file to a json file.
I follow this question to write a Makefile like this.
SRCS = $(wildcard docs/csv/*.csv)
DESTS = $(patsubst docs/csv/%.csv, scripts/data/%.lua, $(SRCS))
all: $(DESTS)
$(DESTS): $(SRCS)
echo $#
echo $<
but every time I ran make all, the echo $# show every file as expected, but echo $< always show the single file, called items.csv in my csv folder.
The trouble is that in this rule:
$(DESTS): $(SRCS)
...
every lua file depends on all csv files, which is not what I think you intend. And since $< expands to the first prerequisite, you get the same one (items.csv) for every target.
Try this:
all: $(DESTS)
scripts/data/%.lua: docs/csv/%.csv
echo $#
echo $<
$<
is the name of the FIRST dependency. Use $^ for all the dependencies
$#
is the name of the current target
The GNU make man page on Automatic Variables is extremely useful. Here's what it says:
$#
The file name of the target of the rule. If the target is an archive member, then ‘$#’ is the name of the archive file. In a
pattern rule that has multiple targets (see Introduction to Pattern
Rules), ‘$#’ is the name of whichever target caused the rule's recipe
to be run.
$<
The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by
the implicit rule (see Implicit Rules).
Incidentally, you probably want to write your make rule as a pattern rule instead:
%.lua : %.csv
<rules for making a lua from a csv>

Resources