More about GNU make
When you are using an integrated development environment (IDE) for software development you initially have to select whether your project is an application or a library. Roughly spoken writing an application means that you write a program for a user to solve a specific problem. Whereas writing a library means to produce software that other developers can use for an implementation of an application or another (eventually more high-level) library.
In this class the focus is rather on developing libraries than applications. However, you can consider that as the more general case. Every library should have tests for its implementation, and as tests are executable programs we also develop applications. The difference is that our applications are more limited in there scope (each test just checks some small portion of the library) and we have many tests.
Why you should cope with makefiles
Dealing with GNU make in more detail can be motivated as giving you an idea what happens in an IDE when you click a run or build button. And also giving you a principal understanding what kind of preferences each IDE provides for configuring the underlying build system. In my opinion teaching the general concepts make more sense than teaching a particular IDE. While there are many IDEs out there details differ and will change. Once you learned and understood the underlying concept you easily can adapt to the special case.
We need some pet project
For developing some useful software for the ULM we need some kind of a C standard library. In its initial stage it contains just three functions that are implemented in separate translation units:
-
The startup code that initializes the stack, calls main and halts the ULM with the return value of main.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
.equ FP, 1 .equ SP, 2 .equ RET, 3 //------------------------------------------------------------------------------ // Function _start() //------------------------------------------------------------------------------ .equ ret, 0 .equ fp, 8 .equ rval, 16 .text .globl _start _start: // begin of the function body ldzwq 0, %SP // call function main() subq 24, %SP, %SP ldzwq main, %4 jmp %4, %RET movq rval(%SP), %4 addq 24, %SP, %SP halt %4
-
Procedure puts prints a string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
.equ FP, 1 .equ SP, 2 .equ RET, 3 //------------------------------------------------------------------------------ // Procedure puts(str) //------------------------------------------------------------------------------ .equ ret, 0 .equ fp, 8 // procedure arguments .equ str, 16 .text .globl puts puts: // function prologue movq %RET, ret(%SP) movq %FP, fp(%SP) addq 0, %SP, %FP // reserve space for 0 local variables. subq 0, %SP, %SP // begin of the function body /* if (*str == 0) goto puts.leave; */ puts.while movq str(%FP), %4 movzbq (%4), %4 subq 0, %4, %0 jz puts.while.done /* putchar(*str); */ movq str(%FP), %4 movzbq (%4), %4 putc %4 /* str = str + 1; */ movq str(%FP), %4 addq 1, %4, %4 movq %4, str(%FP) /* goto puts.while; */ jmp puts.while puts.while.done: // end of the function body // function epilogue puts.leave: addq 0, %FP, %SP movq fp(%SP), %FP movq ret(%SP), %RET jmp %RET, %0
-
Procedure putui prints an unsigned integer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
.equ FP, 1 .equ SP, 2 .equ RET, 3 //------------------------------------------------------------------------------ // Procedure putui(n) //------------------------------------------------------------------------------ .equ ret, 0 .equ fp, 8 // procedure arguments .equ n, 16 // local variables .equ p, -8 .equ buf, p-22 .text .globl putui putui: // function prologue movq %RET, ret(%SP) movq %FP, fp(%SP) addq 0, %SP, %FP // reserve space for pointer p and array buf with 22 characters subq 22, %SP, %SP // begin of the function body /* p = buf; */ ldswq buf, %4 addq %4, %FP, %4 movq %4, p(%FP) /* do { */ putui.do: /* *p = n % 10 + '0'; */ movq n(%FP), %4 ldzwq 0, %5 divq 10, %4, %4 addq '0', %6, %6 movq p(%FP), %7 movb %6, (%7) /* ++p; */ movq p(%FP), %4 addq 1, %4, %4 movq %4, p(%FP) /* n /= 10; */ movq n(%FP), %4 divq 10, %4, %4 movq %4, n(%FP) /* } while (n!=0); */ movq n(%FP), %4 subq 0, %4, %0 jnz putui.do /* while (p != buf) { */ putui.while: ldswq buf, %4 addq %4, %FP, %4 movq p(%FP), %5 subq %4, %5, %0 jz putui.while_done /* --p; */ movq p(%FP), %4 subq 1, %4, %4 movq %4, p(%FP) /* putchar(*p); */ movq p(%FP), %4 movzbq (%4), %4 putc %4 jmp putui.while putui.while_done: // end of the function body // function epilogue putui.leave: addq 0, %FP, %SP movq fp(%SP), %FP movq ret(%SP), %RET jmp %RET, %0
Whatever the library implements needs to be tested. For that we initially have two programs:
-
xhello is testing puts by printing “hello, world”.
theon$ xhello hello, world! theon$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
.equ FP, 1 .equ SP, 2 .equ RET, 3 //------------------------------------------------------------------------------ // Global variables //------------------------------------------------------------------------------ .data msg .string "hello, world!\n" //------------------------------------------------------------------------------ // Function main() //------------------------------------------------------------------------------ .equ ret, 0 .equ fp, 8 .equ rval, 16 .text .globl main main: // function prologue movq %RET, ret(%SP) movq %FP, fp(%SP) addq 0, %SP, %FP // begin of the function body /* puts(msg); */ // call procedure puts(msg) subq 24, %SP, %SP # store argument msg in 16(%SP) ldzwq msg, %4 movq %4, 16(%SP) ldzwq puts, %4 jmp %4, %RET addq 24, %SP, %SP /* return 0; */ ldzwq 0, %4 movq %4, rval(%FP) jmp main.leave // end of the function body // function epilogue main.leave: addq 0, %FP, %SP movq fp(%SP), %FP movq ret(%SP), %RET jmp %RET, %0
-
xanswer is testing putui by printing 42.
theon$ xanswer The answer is 42 theon$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
.equ FP, 1 .equ SP, 2 .equ RET, 3 //------------------------------------------------------------------------------ // Global variables //------------------------------------------------------------------------------ .data msg0 .string "The answer is " msg1 .string "\n" //------------------------------------------------------------------------------ // Function main() //------------------------------------------------------------------------------ .equ ret, 0 .equ fp, 8 .equ rval, 16 .text .globl main main: // function prologue movq %RET, ret(%SP) movq %FP, fp(%SP) addq 0, %SP, %FP // begin of the function body /* puts(msg0); */ // call procedure puts(msg) subq 24, %SP, %SP # store argument msg0 in 16(%SP) ldzwq msg0, %4 movq %4, 16(%SP) ldzwq puts, %4 jmp %4, %RET movq rval(%SP), %4 addq 24, %SP, %SP /* putui(42); */ // call procedure putui(msg) subq 24, %SP, %SP # store argument 42 in 16(%SP) ldzwq 42, %4 movq %4, 16(%SP) ldzwq putui, %4 jmp %4, %RET addq 24, %SP, %SP /* puts(msg1); */ // call procedure puts(msg) subq 24, %SP, %SP # store argument msg1 in 16(%SP) ldzwq msg1, %4 movq %4, 16(%SP) ldzwq puts, %4 jmp %4, %RET addq 24, %SP, %SP /* return 0; */ ldzwq 0, %4 movq %4, rval(%FP) jmp main.leave // end of the function body // function epilogue main.leave: addq 0, %FP, %SP movq fp(%SP), %FP movq ret(%SP), %RET jmp %RET, %0
Simplifications
It certainly would be a good idea to separate source files that are part of the library form those that are used for testing. But this will make things in the beginning unnecessarily complicated (it in general requires hierarchical makefiles, i.e. makefiles that call makefiles etc.). Still we somehow have to distinguish in the build system between these two categories. A practical solution is using naming conventions. If a source file begins with the character 'x' then it's assumed to be a test program and otherwise part of the library.
For now we also make the oversimplified assumption that all tests depend on all object files of the library part. So all our tests will contain code from all the object files even if some of the code is not needed. We take care of this unnecessarily large executables later by using bigger guns (in form of static libraries).
Translating and linking manually
theon$ ulmas -o xhello.o xhello.s theon$ ulmas -o xanswer.o xanswer.s theon$ ulmas -o crt0.o crt0.s theon$ ulmas -o puts.o puts.s theon$ ulmas -o putui.o putui.s theon$ ulmld -o xhello xhello.o crt0.o puts.o putui.o theon$ ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$
Motivation to consider static libraries next
Check that xhello contains the object code putui.o which is never used. Yes, look into the executable!
Typical use-cases for developing a library
We want a build system that allows to simply add new source files to the library, and also allows to add new tests.
You don't want to write makefiles like this
This is a handwritten makefile in the style of Quiz 13 from the previous page. It is certainly not suitable when you want to add new source files to your project. But that is what we start with. Because it is easy to see that it satisfies the dependencies stated in the above graph:
all: xhello xanswer
clean:
rm -f xhello xanswer crt0.o puts.o putui.o
xanswer: xanswer.o crt0.o puts.o putui.o
ulmld -o xanswer xanswer.o crt0.o puts.o putui.o
xhello: xhello.o crt0.o puts.o putui.o
ulmld -o xhello xhello.o crt0.o puts.o putui.o
xhello.o: xhello.s
ulmas -o xhello.o xhello.s
xanswer.o: xanswer.s
ulmas -o xanswer.o xanswer.s
crt0.o: crt0.s
ulmas -o crt0.o crt0.s
puts.o: puts.s
ulmas -o puts.o puts.s
putui.o: putui.s
ulmas -o putui.o putui.s
Check the following things:
-
If you modify puts.s then puts.o gets updated, i.e. the assembler gets called. Also xhello and xanswer gets update by using the linker. Hence the following needs to be done:
1 2 3
ulmas -o puts.o puts.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o
-
If you modify xhello.s only xhello.o get updated with the assembler and the used to update xhello. Hence the following needs to be done:
1 2
ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o puts.o putui.o
Now check that the makefile is doing the right thing. Use the touch command to update the modification time of a file, i.e. let make think a file was modified:
theon$ make ulmas -o crt0.o crt0.s ulmas -o puts.o puts.s ulmas -o putui.o putui.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o puts.o putui.o theon$ make clean rm -f xhello xanswer crt0.o puts.o putui.o theon$
Using variables
In a next step we make only minor improvements to the makefile. We use variables to define what command gets used to translate assembly code and to link object files:
1 2 3 4 | AS := ulmas
LD := ulmld
TestTargets := xhello xanswer
|
With $(AS) and $(LD) we can refer to the values of these variables in the rest of the makefile, in particular the rules. Note that GNU make support different flavors of variables. Here we use the flavor you get when you define the variable with :=. Which simply means that it has a fixed value, and you can use the variable after its definition. The other flavor will be used (and motivated) on the next page when we build a static library.
Selecting in one place what tools get used to do the job is certainly a good ides. In an IDE you usually can select somewhere in the preference what tool for building a project can be used, e.g. which C compiler (Gnu C compiler, Intel compiler, Microsoft compiler, Sun Compiler, etc.). But there is more about it. The identifiers AS and LD where not chosen arbitrary. In an upcoming step we will exploit that these are variables used by implicit rules. For now just observe that the rules will be changed to something like this:
1 2 | xanswer: xanswer.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
Variables $@ and $^ are so called automatic variables. In the command you can refer with $@ to the target of the rule (i.e. the left hand side of the colon) , and with $^ you can refer to the list of dependencies (i.e. the right hand side of the colon). So altogether this is equivalent to
1 2 | xanswer: xanswer.o crt0.o puts.o putui.o
ulmld -o xanswer xanswer.o crt0.o puts.o putui.o
|
Now check that you understand the rest of the makefile:
AS := ulmas
LD := ulmld
TestTargets := xhello xanswer
all: $(TestTargets)
clean:
$(RM) $(TestTargets) *.o
xanswer: xanswer.o crt0.o puts.o putui.o
$(LD) -o $@ $^
xhello: xhello.o crt0.o puts.o putui.o
$(LD) -o $@ $^
xhello.o: xhello.s
$(AS) -o $@ $^
xanswer.o: xanswer.s
$(AS) -o $@ $^
crt0.o: crt0.s
$(AS) -o $@ $^
puts.o: puts.s
$(AS) -o $@ $^
putui.o: putui.s
$(AS) -o $@ $^
Also check that it is acting as before:
theon$ make ulmas -o xhello.o xhello.s ulmas -o crt0.o crt0.s ulmas -o puts.o puts.s ulmas -o putui.o putui.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmas -o xanswer.o xanswer.s ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o puts.o putui.o theon$ make clean rm -f xhello xanswer *.o theon$
Using pattern rules
Using pattern rules we can reduces the size of the makefile. In the makefile we define
1 2 | %.o: %.s
$(AS) -o $@ $^
|
Other rules like
1 2 | xanswer: xanswer.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
will trigger the above pattern rule for checking the dependencies of its object files, so altogether this makefile is equivalent to before:
AS := ulmas
LD := ulmld
TestTargets := xhello xanswer
all: $(TestTargets)
clean:
$(RM) $(TestTargets) *.o
xanswer: xanswer.o crt0.o puts.o putui.o
$(LD) -o $@ $^
xhello: xhello.o crt0.o puts.o putui.o
$(LD) -o $@ $^
%.o: %.s
$(AS) -o $@ $^
theon$ make ulmas -o xhello.o xhello.s ulmas -o crt0.o crt0.s ulmas -o puts.o puts.s ulmas -o putui.o putui.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmas -o xanswer.o xanswer.s ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o puts.o putui.o theon$ make clean rm -f xhello xanswer *.o theon$
Using static pattern rules
We also want to generalize the rules for building the tests. In a first attempt we would probably try the following pattern rule:
1 2 | %: %.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
This should work because the first rule
1 | all: $(TestTargets)
|
has dependencies xhello and xanswer (these are the values of TestTargets). Hence the pattern rule would match when make substitutes % accordingly. However, there are so called implicit rules (see below) built-in, and one of them is
1 2 3 | %: %.s
# recipe to execute (built-in):
$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
If more than one rule matches the one with fewer dependencies is preferred. To work around this issue we could use the more restrictive pattern rule
1 2 | x%: x%.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
This works for us because only source files beginning with the character are test programs. A more general solution can be achieved with static pattern rules. With
1 2 | $(TestTargets): %: %.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
we define an dedicated rule for the targets listed in $(TestTargets). In our case this is just `xhello` and `xanswer` but overall we have a solution that works for an arbitrary list of targets:
AS := ulmas
LD := ulmld
TestTargets := xhello xanswer
all: $(TestTargets)
clean:
$(RM) $(TestTargets) *.o
#x% : x%.o crt0.o puts.o putui.o
# $(LD) -o $@ $^
$(TestTargets): % : %.o crt0.o puts.o putui.o
$(LD) -o $@ $^
%.o: %.s
$(AS) -o $@ $^
theon$ make ulmas -o xhello.o xhello.s ulmas -o crt0.o crt0.s ulmas -o puts.o puts.s ulmas -o putui.o putui.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmas -o xanswer.o xanswer.s ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xhello xhello.o crt0.o puts.o putui.o ulmld -o xanswer xanswer.o crt0.o puts.o putui.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o puts.o putui.o theon$ make clean rm -f xhello xanswer *.o theon$
Using wildcards and text functions
So far we explicitly list the test programs in the makefile with
1 | TestTargets := xhello xanswer
|
And also the objects files for the library in this rule:
1 2 | $(TestTargets): % : %.o crt0.o puts.o putui.o
$(LD) -o $@ $^
|
Using wildcards and text functions we can make let create this list automatically. In the following makefile $(wildcard x*.s) expands to the list all files that begin with 'x' and have the extension '.s'. Currently that is xhello.s and xanswer.s. When new test programs are added the list automatically adapts. With
1 | LibSources := $(filter-out x%.s,$(wildcard *.s))
|
We get a list of all source files for the library. The wildcard function is first used to get all files with extension '.s' and the filter-out function to remove files that begin with 'x'. With that the variable LibSources is set to the space-separated list with entries crt0..s, puts.s and putui.s. Finally the patsubst (pattern substitution) function is used to replace the extension '.s' with '.o'. With that the variable LibObjects is set to the list of object files for the library.
Hence with the following makefile allows that new files for library of test programs simply can be added to the directory:
AS := ulmas
LD := ulmld
TestTargets := $(patsubst %.s,%,$(wildcard x*.s))
LibSources := $(filter-out x%.s,$(wildcard *.s))
LibObjects := $(patsubst %.s,%.o,$(LibSources))
all: $(TestTargets)
clean:
$(RM) $(TestTargets) $(Objects)
$(TestTargets): % : %.o $(LibObjects)
$(LD) -o $@ $^
%.o: %.s
$(AS) -o $@ $^
theon$ make make: Nothing to be done for 'all'. theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ make clean rm -f xanswer xhello theon$
Using implicit rules
Above implicit rules where already mentioned above. With make -p you get a (long) list of buit-in rules:
theon$ make -p ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmld -o xhello xhello.o crt0.o putui.o puts.o # GNU Make 4.2.1 # Built for i386-pc-solaris2.11 # Copyright (C) 1988-2016 Free Software Foundation, Inc. # License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> # This is free software: you are free to change and redistribute it. # There is NO WARRANTY, to the extent permitted by law. # Make data base, printed on Tue Jul 14 15:37:13 2020 # Variables # environment DOC_PROJECT = /home/numerik/hpc/ss20/hpc0 # automatic <D = $(patsubst %/,%,$(dir $<)) # environment SHELL_HOME_DIR = /home/numerik/hpc/ss20/hpc0 # automatic ?F = $(notdir $?) # default .SHELLFLAGS := -c # environment LC_CTYPE = en_US.UTF-8 # default CWEAVE = cweave # automatic ?D = $(patsubst %/,%,$(dir $?)) # environment JS_FOLLOWSCROLL = /home/numerik/lehn/DocTool/Templates/tip_followscroll.js # automatic @D = $(patsubst %/,%,$(dir $@)) # automatic @F = $(notdir $@) # default PC = pc # makefile CURDIR := /home/numerik/hpc/ss20/hpc0/session11/make05 # makefile SHELL = /bin/sh # environment VIM = vim # default CO = co # default COMPILE.mod = $(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH) # environment _ = /opt/ulm/ballinrobe/bin/make # default PREPROCESS.F = $(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F # default LINK.m = $(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) # default LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH) # default OUTPUT_OPTION = -o $@ # environment DB_DIR = /home/numerik/hpc/ss20/hpc0/db # environment A__z = "*SHLVL # environment FILETREE_HEADER = /home/numerik/lehn/DocTool/Templates/FiletreeHeader # makefile (from 'Makefile', line 1) MAKEFILE_LIST := Makefile # 'override' directive GNUMAKEFLAGS := # default LINK.p = $(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) # environment MAKELEVEL := 0 # environment XDG_DATA_DIRS = /opt/ulm/ballinrobe/share # environment REMOTE_HOST = 46.223.116.37 # default CC = cc # environment LATEX_FOOTER = /home/numerik/lehn/DocTool/Templates/LatexFooter.tex # environment LC_COLLATE = # default CHECKOUT,v = +$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@) # environment CODE_DIR = /home/numerik/hpc/ss20/hpc0 # environment USER = lehn # environment MAIL = /var/mail/lehn # environment INDEX_HEADER = /home/numerik/hpc/ss20/hpc0/IndexHeader # default CPP = $(CC) -E # default LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) # default COMPILE.S = $(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c # environment SSH_CONNECTION = 46.223.116.37 51997 134.60.66.7 22 # environment PATH = .:/home/numerik/lehn/bin:/opt/ulm/ballinrobe/cmd:/opt/ulm/ballinrobe/bin:/usr/bin:/usr/perl5/5.12/bin:/home/numerik/lehn/DocTool/Executables:/home/numerik/lehn/DocTool/Executables # environment PERL_BADLANG = # makefile (from 'Makefile', line 2) LD := ulmld # default TEXI2DVI = texi2dvi # environment LATEX_FORMULA = /home/numerik/lehn/DocTool/Templates/LatexFormula.tex # default YACC = yacc # environment QMAILNAME = Michael Lehn # environment SSH_TTY = /dev/pts/14 # environment SSH_CLIENT = 46.223.116.37 51997 22 # default ARFLAGS = rv # default LINK.r = $(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH) # default LINT = lint # default COMPILE.f = $(FC) $(FFLAGS) $(TARGET_ARCH) -c # default LINT.c = $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH) # default YACC.m = $(YACC) $(YFLAGS) # environment LC_MESSAGES = # default YACC.y = $(YACC) $(YFLAGS) # default AR = ar # environment TEMPLATE_DIR = /home/numerik/lehn/DocTool/Templates # environment LC_NUMERIC = # environment IMAGE_DIR = /home/www/htdocs/numerik/hpc/ss20/hpc0/Images # default .FEATURES := target-specific order-only second-expansion else-if shortest-stem undefine oneshell archives jobserver output-sync check-symlink load # default TANGLE = tangle # environment SOURCEFILE_FOOTER = /home/numerik/lehn/DocTool/Templates/SimpleFooter # default GET = get # environment AUTHOR_WEBSITE = https://www.uni-ulm.de/mawi/mawi-numerik/institut/mitarbeiter/mlehn/ # automatic %F = $(notdir $%) # makefile (from 'Makefile', line 7) LibObjects := crt0.o putui.o puts.o # default COMPILE.F = $(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c # environment JS_TOOLTIP = /home/numerik/lehn/DocTool/Templates/wz_tooltip.js # default CTANGLE = ctangle # environment VISUAL = /opt/ulm/ballinrobe/bin/vim # environment _AST_FEATURES = UNIVERSE - att # environment SLIDE_FOOTER = /home/numerik/lehn/DocTool/Templates/SimpleFooter # default .LIBPATTERNS = lib%.so lib%.a # default LINK.C = $(LINK.cc) # environment PWD = /home/numerik/hpc/ss20/hpc0/session11/make05 # default .LOADED := # default LINK.S = $(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH) # default PREPROCESS.r = $(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F # automatic *D = $(patsubst %/,%,$(dir $*)) # environment PKG_CONFIG_PATH = /opt/ulm/ballinrobe/share/pkgconfig:/opt/ulm/ballinrobe/lib/amd64/pkgconfig:/opt/ulm/ballinrobe/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig # environment JS_CENTERWINDOW = /home/numerik/lehn/DocTool/Templates/tip_centerwindow.js # default LINK.s = $(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH) # environment HOME = /home/numerik/lehn # automatic +D = $(patsubst %/,%,$(dir $+)) # environment LOGNAME = lehn # environment TTYRESET = 2d02:1805:f00bf:8a3b:3:1c:7f:15:4:0:0:0:11:13:1a:19:12:f:17:16:0:0:1:1:0:00:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0 # automatic ^D = $(patsubst %/,%,$(dir $^)) # environment LC_TIME = # environment VIMRUNTIME = /usr/local/share/vim/vim74 # default COMPILE.m = $(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c # default MAKE = $(MAKE_COMMAND) # environment SHLVL = 3 # environment RSYNC_RSH = /usr/bin/ssh # makefile (from 'Makefile', line 1) AS := ulmas # default PREPROCESS.S = $(CC) -E $(CPPFLAGS) # default COMPILE.p = $(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c # default MAKE_VERSION := 4.2.1 # environment EDITOR = /opt/ulm/ballinrobe/bin/vim # default FC = f77 # environment LC_MONETARY = # makefile .DEFAULT_GOAL := all # environment QMAILINJECT = if # makefile (from 'Makefile', line 4) TestTargets := xanswer xhello # environment QMAILUSER = michael.lehn # environment JS_SCRIPT = /home/numerik/lehn/DocTool/Templates/graph.js # automatic %D = $(patsubst %/,%,$(dir $%)) # environment QMAILHOST = uni-ulm.de # environment LOGINPID = 23714 # environment LATEX_HEADER = /home/numerik/lehn/DocTool/Templates/LatexHeader.tex # default WEAVE = weave # default MAKE_COMMAND := make # default LINK.cpp = $(LINK.cc) # default F77 = $(FC) # environment OLDPWD = /home/numerik/lehn # default .VARIABLES := # environment TMPDIR = /tmp # automatic *F = $(notdir $*) # environment LC_TERMINAL = iTerm2 # environment OSFONTDIR = /opt/ulm/ballinrobe/share/fonts:/usr/share/fonts/TrueType # default COMPILE.cpp = $(COMPILE.cc) # default COMPILE.def = $(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH) # default LEX = lex # default RM = rm -f # environment ARCH = i86pc # makefile MAKEFLAGS = p # environment MFLAGS = -p # environment DEFAULT_PROJECT_NAME = /home/numerik/hpc/ss20/hpc0 # environment LOG_DIR = /home/numerik/hpc/ss20/hpc0/log # default LEX.l = $(LEX) $(LFLAGS) -t # default LEX.m = $(LEX) $(LFLAGS) -t # environment LC_TERMINAL_VERSION = 3.3.12 # environment DOCTOOL = /home/numerik/lehn/DocTool # default COMPILE.r = $(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c # environment AUTHOR = Michael C. Lehn # automatic +F = $(notdir $+) # default M2C = m2c # environment DO_PROFILE = 1 # environment DEFAULT_CSS = /home/numerik/lehn/DocTool/Templates/default.css # default MAKEFILES := # default COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c # environment CONFIG_SHELL = /usr/bin/ksh # automatic <F = $(notdir $<) # environment FILETREE_FOOTER = /home/numerik/lehn/DocTool/Templates/SimpleFooter PS1 = theon$
This contains
1 2 3 | %.o: %.s
# recipe to execute (built-in):
$(COMPILE.s) -o $@ $<
|
and the definition
1 | COMPILE.s = $(AS) $(ASFLAGS) $(TARGET_MACH)
|
The variables ASFLAGS and TARGET_MACH are in our case not defined and therefore will be expanded to empty strings. Hence this default rule is equivalent to our rule
1 2 | %.o: %.s
$(AS) -o $@ $^
|
and we simply remove it:
AS := ulmas
LD := ulmld
TestTargets := $(patsubst %.s,%,$(wildcard x*.s))
LibSources := $(filter-out x%.s,$(wildcard *.s))
LibObjects := $(patsubst %.s,%.o,$(LibSources))
all: $(TestTargets)
clean:
$(RM) $(TestTargets) *.o
$(TestTargets): % : %.o $(LibObjects)
$(LD) -o $@ $^
theon$ make ulmas -o xanswer.o xanswer.s ulmas -o crt0.o crt0.s ulmas -o putui.o putui.s ulmas -o puts.o puts.s ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ make clean rm -f xanswer xhello *.o theon$
Phony targets
A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. In our case these are the targets all and clean:
AS := ulmas
LD := ulmld
TestTargets := $(patsubst %.s,%,$(wildcard x*.s))
LibSources := $(filter-out x%.s,$(wildcard *.s))
LibObjects := $(patsubst %.s,%.o,$(LibSources))
.PHONY: all clean
all: $(TestTargets)
clean:
$(RM) $(TestTargets) *.o
$(TestTargets): % : %.o $(LibObjects)
$(LD) -o $@ $^
theon$ make ulmas -o xanswer.o xanswer.s ulmas -o crt0.o crt0.s ulmas -o putui.o putui.s ulmas -o puts.o puts.s ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ touch puts.s theon$ make ulmas -o puts.o puts.s ulmld -o xanswer xanswer.o crt0.o putui.o puts.o ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ touch xhello.s theon$ make ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o crt0.o putui.o puts.o theon$ make clean rm -f xanswer xhello *.o theon$