How to write a makefile? - makefile

I have just discovered that a LaTeX compilation could be launched from a makefile. I came to this from the need to generate a series of targets with some alterations of parameters.
I have the file at the bottom. How can I add other compilation jobs, with different optional parameters say. Said otherwise, how to change the name of the target file(s) when there is more than one?
MyFileNew.pdf : MyFile.tex
pdflatex "\def\UseOption{nonumber,nographics,e} \input{MyFile.tex}"
#makefile
MyFile.pdf : MyFile.tex
pdflatex "\def\UseOption{number,graphics,ef} \input{MyFile.tex}"\
pdflatex "\def\UseOption{number,graphics,ef} \input{MyFile.tex}"
#echo «Removing auxilliary LaTeX files resulting from compilation»
#rm -f *.log *.aux *.dvi *.toc *.lot *.lof
#end of makefile

Are you perhaps looking for something like this?
pdflatex := pdflatex "\def\UseOption{$(options_$*)} \input{$<}"
cleanup := rm -f *.log *.aux *.dvi *.toc *.lot *.lof
options_MyFileNew := nonumber,nographics,e
options_MyFile := number,graphics,ef
MyFileNew.pdf MyFile.pdf: %.pdf: MyFile.tex
$(pdflatex)
$(pdflatex)
$(cleanup)
This is basically just a refactoring of what you already have.

The following works finally as expected (except for cleaning the auxilliary files).
Makefile
.PHONY: all clean clean-all
all : TargetFile1.pdf TargetFile2.pdf
TargetFile1.pdf : MyFile.tex
pdflatex --jobname=$(#:.pdf=) "\def\UseOption{number,nographics,e} \input{MyFile.tex}"
TargetFile2.pdf : MyFile.tex
pdflatex --jobname=$(#:.pdf=) "\def\UseOption{number,nographics,ef} \input{MyFile.tex}"
clean :
rm -f *.log *.aux *.dvi *.toc *.lof
clean-all : clean
rm -fr *.pdf
# Makefile

Simple make file
all: main.o module.o \\ The dependencies of the target all \
gcc main.o module.o -o target_bin \\ The action to make the target all \
main.o: main.c module.h \\ The dependencies fo the target main.o \
gcc -I . -c main.c \\ The action to make the target main.o ,\
module.o: module.c module.h \
gcc -I . -c module.c \\ -I tells the compiler header file locations \
clean: \\ This target has no dependencies \
rm -rf *.o \
rm target_bin
Answering this question requires a relatively long explanation and a multi files for demonstration, I included a HERE a link to a github repo that answers this question in a compact (with enough details).

Related

Makefile does not invoke action after touching the target file

Here is my Makefile:
NAME := libftprintf.a
LIB := ar rcs
CC := gcc
CFLAGS := -Wall -Wextra -Werror
SRCS_DIR := ./sources/
HDRS_DIR := ./headers/
OBJS_DIR := ./objectives/
SRCS_FILES := ft_conv.c \
ft_eval_hex.c \
ft_eval_number.c \
ft_eval_string.c \
ft_parsers.c \
ft_strs_join.c \
ft_eval_char.c \
ft_eval_int.c \
ft_eval_percent.c \
ft_handler.c \
ft_printf.c
HDRS_FILES := ft_conv.h \
ft_eval_hex.h \
ft_eval_number.h \
ft_eval_string.h \
ft_parsers.h \
ft_strs_join.h \
ft_eval_char.h \
ft_eval_int.h \
ft_eval_percent.h \
ft_handler.h \
ft_printf.h
OBJS_FILES := $(SRCS_FILES:.c=.o)
SRCS := $(addprefix $(SRCS_DIR),$(SRCS_FILES))
HDRS := $(addprefix $(HDRS_DIR),$(HDRS_FILES))
OBJS := $(addprefix $(OBJS_DIR),$(OBJS_FILES))
LIBFT_DIR := ./libft/
LIBFT_NAME := libft.a
LIBFT := $(addprefix $(LIBFT_DIR),$(LIBFT_NAME))
RM := rm -rf
all: $(NAME)
bonus: all
bonus_one: all
bonus_two: all
$(NAME): $(OBJS)
$(LIB) $(NAME) $(OBJS) $(LIBFT)
$(OBJS_DIR)%.o: $(SRCS_DIR)%.c $(HDRS) Makefile | $(OBJS_DIR) subsystem
$(CC) $(CFLAGS) -c $< -o $# -I $(HDRS_DIR) -I $(LIBFT_DIR)
subsystem:
#$(MAKE) -C $(LIBFT_DIR)
$(OBJS_DIR):
mkdir $(OBJS_DIR)
clean:
#$(MAKE) -C $(LIBFT_DIR) clean
$(RM) $(OBJS_DIR)
fclean: clean
#$(MAKE) -C $(LIBFT_DIR) fclean
$(RM) $(NAME)
re: fclean all
.PHONY: all subsystem bonus bonus_one bonus_two clean fclean re
The problem is so: if I do make all, then touch libftprintf.a I would expect that make all will rebuild libftprintf.a since it was changed and target all depends on that file. However, make does nothing and I can't understand this behavior.
Also, there is one more minor issue: during make all I create a directory objectives where I store all .o files. Calling make fclean and make all entirely rebuilds the target, however calling make re results in an error:
rm -rf ./objectives/
rm -rf *libft objectives here*
rm -rf libft.a
rm -rf libftprintf.a
make: *** No rule to make target `objectives', needed by `objectives/ft_conv.o'. Stop.
If I call make re right after this error, the target builds as always. Also, if I change re target to this, I do not receive any errors:
re:
#$(MAKE) fclean
#$(MAKE) all
Could not find solutions to my problems anywhere on the Internet.
Touching libprintf.a won't cause anything to rebuild. Make rebuilds targets that are out of date. Out of date means that either the target doesn't exist, or some prerequisite of the target is newer than the target.
After you run make then libprintf.a is up to date, which means it's newer than all its prerequisites. Running touch libprintf.a just makes it even newer than its prerequisites than it was before, so make still considers it up to date.
If you want to rebuild libprintf.a you need to touch (or delete) one of its prerequisites, not the target itself.
The reason for your second issue seems to be related to the makefile in the libft subdirectory. It seems like you're using the --no-print-directories option here: you should avoid using that, at least as long as you're debugging, so that you can see where make is going and which makefile it's running.

Using make to compile several files of the same type which are under different subdirectories?

I just started learning make today. I have several assembly files that I want to compile and then join into a single file. For now I have two files in my tree but the makefile code should be able to handle more. So here is what the files look like.
Src/Boot/MBR.asm
Src/Boot/SecondStage/Bootloader.asm
I want to compile each of these files into the Bin/ directory that the makefile is located in, where the files should end up like this
Bin/MBR.bin
Bin/Bootloader.bin
then I will concentrate these two files into one single file os-image.img
So far I came up with the following
AS := nasm
ASFLAGS_BIN := -fbin
SRCDIR := Src
BINDIR := Bin
BOOTASM = $(shell find $(SRCDIR) -name '*.asm')
BOOTBIN = $(addprefix $(BINDIR)/, $(addsuffix .bin, $(basename $(notdir $(BOOTASM)))))
build: clean compile
cat $(BOOTBIN) > Bin/os-image.img
compile: $(BOOTBIN)
$(BOOTBIN) : $(BOOTASM)
$(AS) $(ASFLAGS_BIN) $< -o $#
clean:
rm -rf $(BINDIR)/%.bin
The output when I type make to shell is the following
rm -rf Bin/%.bin
nasm -fbin Src/Boot/second/Bootloader.asm -o Bin/Bootloader.bin
nasm -fbin Src/Boot/second/Bootloader.asm -o Bin/MBR.bin
cat Bin/Bootloader.bin Bin/MBR.bin > Bin/os-image.img
The expected output is:
rm -rf Bin/%.bin
nasm -fbin Src/Boot/second/Bootloader.asm -o Bin/Bootloader.bin
nasm -fbin Src/Boot/second/MBR.asm -o Bin/MBR.bin
cat Bin/Bootloader.bin Bin/MBR.bin > Bin/os-image.img
Obviously the problem is in here
$(BOOTBIN) : $(BOOTASM)
$(AS) $(ASFLAGS_BIN) $< -o $#
However I couldn't understand how I should be able to achieve what I want since I am pretty inexperienced at this.
So the question is:
How should I get each prerequisite that corresponds to the related target? or something similar to that.
Thanks in advance.
You can do this with VPATH --
BOOTASM = $(shell find $(SRCDIR) -name '*.asm')
BOOTBIN = $(addprefix $(BINDIR)/, $(addsuffix .bin, $(basename $(notdir $(BOOTASM)))))
VPATH=$(sort $(dir $(BOOTASM))
$(BOOTBIN) : %.bin : %.asm
$(AS) $(ASFLAGS_BIN) $< -o $#
But, read the third rule of makefiles before you got to far down the vpath road...
Another thing you should be aware of -- for
build: clean compile
cat $(BOOTBIN) > Bin/os-image.img
Then clean is not guaranteed to run before compile (and in fact, on a parallel system they might both try to run at the same time...). Obviously this would not be what you want. Either make compile depend on clean (but then it will clean every time you try to compile), or create a seperate clean_then_compile : clean target, which runs the compile command on its own.

Simple makefile command not found

I was given a makefile that looks like this, and told not to change it.
all: clean flex scanner.lex bison -d parser.ypp g++ -std=c++11 -o hw2 *.c *.cpp clean: rm -f lex.yy.c rm -f parser.tab.*pp rm -f hw2
I am trying to run this makefile in a folder with files named: scanner.lex, parser.ypp, output.hpp and output.cpp
I copied it to a file like this:
all:
clean flex scanner.lex bison -d parser.ypp g++ -std=c++11 -o hw2 *.c *.cpp
clean:
rm -f lex.yy.c rm -f parser.tab.*pp rm -f hw2
When I run the make command in my terminal I get an error:
clean flex scanner.lex bison -d parser.ypp g++ -std=c++11 -o hw2 *.c *.cpp
/bin/sh: clean: command not found
make: *** [all] Error 127
Am I doing something wrong? Again, I was given this line and told not to change it.
Thanks a lot.
Line breaks are essential in most computer environments. If you were given a Makefile without the line breaks and you try to cut it randomly you will have difficulties before if finally works. Try this, maybe:
all: clean
flex scanner.lex
bison -d parser.ypp
g++ -std=c++11 -o hw2 *.c *.cpp
clean:
rm -f lex.yy.c
rm -f parser.tab.*pp
rm -f hw2
And use tabs to indent the indented lines, not spaces.
Explanations: all and clean are what is called a target in make parlance. They are the names of the things you want make to do. clean to delete some files, all to do everything else. The
target: prerequisite1 prerequisite2...
recipe1
recipe2
...
template is the basic make template. It means that target depends on prerequisite1, prerequisite2 and that in order to build it make shall pass recipe1 to the shell for execution, then recipe2...
Note that this Makefile is poorly written. As all and clean are not real file names they should be declared as phony, such that, if a file with that name exists make does the job anyway. As is, it wouldn't. Give it a try:
$ make all
$ touch clean
$ make clean
make: 'clean' is up to date.
See? Because a file named clean exists you cannot make clean anymore, make considers that there is nothing to do for clean. Add this at the beginning of your Makefile:
.PHONY: all clean
A second issue is that make works by comparing last modification times of targets and prerequisites to decide if targets must be rebuilt or not. With your Makefile make will always recompile everything, even if the inputs did not change and the outputs are up-to-date. This is a waste. A better (but untested) Makefile would be something like:
.PHONY: all clean
CFILES := $(filter-out lex.yy.c,$(wildcard *.c))
CPPFILES := $(filter-out parser.tab.cpp,$(wildcard *.cpp))
all: hw2
hw2: lex.yy.c parser.tab.cpp $(CFILES) $(CPPFILES)
g++ -std=c++11 -o $# $^
lex.yy.c: scanner.lex
flex $<
parser.tab.cpp: parser.ypp
bison -d $<
clean:
rm -f lex.yy.c
rm -f parser.tab.*pp
rm -f hw2
Understanding it and why it is better is left as an exercise.

Incremental make fails when source file deleted

I have a simple C program with two source files, and the Makefile generates dependencies automatically, as documented in section 4.14 of the GNU Make Manual:
all: main
%.d: %.c
#set -e; rm -f $#; \
$(CC) -MM $(CPPFLAGS) $< > $#.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $# : ,g' < $#.$$$$ > $#; \
rm -f $#.$$$$
include main.d
include hello.d
main: main.o hello.o
This creates files like main.d that look like so:
main.o main.d : main.c hello.h
The problem comes if I make a change to the source code (and Makefile) to remove hello.c and hello.h. Upon the next incremental rebuild, make fails:
make: *** No rule to make target `hello.h', needed by `main.d'. Stop.
The main.d file is out of date, but make cannot rebuild it since (according to the stale main.d) it still depends on the no-longer-existent hello.h.
In this situation, a clean build will succeed. How can I get the incremental build to succeed as well?
I modified the %.d recipe so that the dependencies are only considered if the file still exists. The new Makefile rule:
%.d: %.c
#set -e; rm -f $#; \
$(CC) -MM $(CPPFLAGS) $< > $#.$$$$; \
sed --in-place 's,\($*\)\.o[ :]*,\1.o $# : ,g' $#.$$$$ ; \
sed 's^: \(.*\)^: $$(foreach t,\1,$$(if $$(wildcard $$t),$$t,))^g' < $#.$$$$ > $#; \
rm -f $#.$$$$
The second sed modifies the main.d to look like:
main.o main.d : $(foreach t,main.c hello.h,$(if $(wildcard $t),$t,))
Thus if any of the dependent files should disappear, make will not complain.
One drawback to this approach is that an incremental build may succeed in situations where a clean build would fail. For example, if hello.h was deleted without properly removing the #include from main.c, then the incremental build would succeed (since it won't try to rebuild main.o) while a full build would fail.
Are there any other drawbacks to this approach? For instance, are there any cases where an incremental build would be incomplete?

Makefile for compiling Markdown files to a single PDF

I am trying to use a Makefile for compiling a PDF when any of a number of Markdown files change:
# Compile report
source := draft
output := dist
sources := $(wildcard $(source)/*.md)
objects := $(patsubst %.md,%.pdf,$(subst$(source),$(output),$(sources)))
all: $(objects)
report-print.md: $(source)/%.md
cat draft/*.md | pandoc \
--variable geometry:a4paper \
--number-sections \
--toc \
--f markdown \
-s \
-o dist/report-print.pdf \
.PHONY : clean
clean:
rm -f $(output)/*.pdf
I get an error:
make: *** No rule to make target `dist/01-title.pdf', needed by `all'. Stop.
The file draft/01-title.md is one of of the source files.
You don't have a rule for creating one .pdf file from one .md file. Which is fine, because that's not what you want to do. You want to create a single pdf file from all the .md files (as I understand it). So, ditch all the objects stuff; you don't need to create all those individual pdf files.
There are a number of other minor problems: you aren't creating the same filename as your target (report-print.md vs. $(output)/report-print.pdf), you should use automatic variables, etc.)
Your makefile will simply be:
source := draft
output := dist
sources := $(wildcard $(source)/*.md)
all: $(output)/report-print.pdf
$(output)/report-print.pdf: $(sources)
cat $^ | pandoc \
--variable geometry:a4paper \
--number-sections \
--toc \
--f markdown \
-s \
-o $#
.PHONY : clean
clean:
rm -f $(output)/*.pdf

Resources