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$