Simple, Simon and Sage. By Gemini 28 Aug, 2025.

Chat about Make

by: burt rosenberg
at: university of miami
date: august 2025

Overview

This is the result of a conversation I had with ChatGPT, in response to the class presentation about make and the Makefile, held the day prior, Monday August 27, 2025. It should lead to a small exercise where the student writes a simple Makefile according to a similar programming task.

The idea was to model a learning through a dialog that the student could have had with ChatGPT. Here is a recording of such a conversation (greatly edited). It was modeled after Galileo's discourses which also had three characters.

After deciding on the characters, I went over to Gemini for an illustration.

Cast of characters

Simon
The knowledgeable programmer. Confident and clear, Simon explains concepts like compiling, linking, and `make` rules. Essentially the expert in the conversation.
Sage
The thoughtful and curious observer. Skeptical at times and not afraid to challenge Simon, Sage voices questions and frustrations that many learners feel.
Simple
The beginner student. Curious, eager to learn, and often reacts to explanations, making the dialogue accessible for readers who are new to programming and `make`.

The Birth of make

Simple: I hear programmers use something called make to build programs. What's that about?
Simon: Ah, make! It was created in 1976 by Stuart Feldman at AT&T Bell Labs. Before that, compiling programs with many files was a headache — you had to run gcc manually on every source file and then link them all together.
Sage: That sounds tedious. Did programmers make mistakes often?
Simon: Very often. Forgetting to recompile a changed file could introduce bugs or crashes. Feldman wrote make to automate the process.
Simple: How did it work?
Simon: You write a Makefile. It lists targets — like an executable — what files they depend on, and the commands to build them. Then make figures out what is out of date and recompiles only what is necessary.
Sage: So it tracks dependencies automatically?
Simon: Exactly. That is the genius of make.
Sage: So how would I actually use make?
Simon: Ah, it is simple in principle. You have targets in your Makefile, and you tell make which target to build.
Simple: What do you mean by "target""?
Simon: A target is something you want to build. The Makefile is a sequence of stanzas like,
target: dependency1 dependency2 ...
    action1
    action2
    ...
    
# note 1: # introduces a comment
# note 2: the indentation for the actions *must be* a tab. Spaces do not work.
and you just type:
make target
Simple: Oh! So that runs the recipe for that target?
Simon: Exactly. It executes the actions associated with that target. It also will call other targets first, if the dependencies are out of date.

A simple example of make

Sage: I wrote the program sum.c, how would I write the Makefile?

// a simple program by a simple programmer

#include <stdio.h>
#include <stdlib.h>


int sum(int a, int b) {
    return a+b ;
}

int main(int argc, char * argv) {
    int a = atoi(argv[1]) ;
    int b = atoi(argv[2]) ;
    printf("%d plus %d is %d\n", a, b, sum(a,b) );
    return 0 ;
}
Simon: Let us create the program sum from the source code sum.c. Since sum is a thing to build, it is a target, of the same name. And since sum comes from the source code sum.c, that's a dependency. We now just have to write the command line that compiles as we wish and use that as the action.
Sage: Can I take a shot at this? Here is what I think will work:
# a simple Makefile for a simple program by a simple programmer

sum: sum.c
    gcc -o sum sum.c
Simple: I see. Now I just type make sum to build my program. But what if I change sum.c, will it rebuild?
Simon:Yes. That's the point of declaring it as a dependency. If you did not declare it as a dependency then that would not happen. But since it is dependency the time stamps on sum and sum.c will be compared. If you change sum.c, the time stamp is updated some make knows to rebuild sum.
Sage: I was wondering, now that we have make, perhaps we can have it help with the use of sum, not just its building. Like, getting ready to archive the program or an example use to make sure the build went well?
Simon: Well, we could have the clean target with no dependencies, that way it is always out of date and therefore it always runs; or a test target.
Sage: Maybe this then?
# a simple Makefile for a simple program by a simple programmer

sum: sum.c
    gcc -o sum sum.c
    
test: sum
    ./sum 3 4 
	
clean:
    -rm sum
Simple: Why the dash?
Simon: Usually if an action fails, make stops right there and declares an error. Dash says to ignore errors and continue. It should not be an error to clean twice.
Simple: Will this be on the test?

A more advanced example of make

/* 
 * sum_util.h
 */

// Function prototype for sum
int sum(int a, int b);




/* 
 * sum_util.c
 */
 
#include "sum_util.h"

// Function definition for sum
int sum(int a, int b) {
    return a + b;
}




/* 
 * sum.c
 */
 
#include <stdio.h>
#include <stdlib.h>

#include "sum_util.h"

int main(int argc, char * argv[]) {
    int a = atoi(argv[1]) ;
    int b = atoi(argv[2]) ;
    printf("%d plus %d is %d\n", a, b, sum(a,b)) ;
    return 0 ;
}
Simon: Let's try something more advanced, to model a realistic situation of many files with interdependences. Let's put the sum function in it's own file called sum_util.c. That's like an implementation of functionality. The declaration of the functionality will be in the sum_util.h file, which is included by any program wanting to use the functionality, as well as in the implementing file itself.
Simple: I tried compiling sum_util.c but it said
     ld: Undefined symbols:
     _main, referenced from:
    
I really am not sure what I should do with this.
Sage: Oh Simple, use the -c flag to ask for compilation only, not to invoke the loading step. Use the -o flag to name the result sum_util.o, where .o means it is an object file.
Simple: So the target should be sum_util.o, because that is the object file that I am making? And its dependencies are sum_util.c and sum_util.h, because if I change either of those, then sum_util.o will need to be rebuil?
Sage: I think so.
Simple: That sounds good. And now sum depends on sum_util.o, so that becomes one of its dependencies. But how do I get sum_util.o combined with the compiled result of sum.c?
Simon: Just name them both on the compiler line. The compiler passes on its work to the loader, including any unused options and object files that didn't need compiling.
Simple: Ok, so here is my Makefile:
sum: sum.c sum_util.o sum_util.h
    gcc -o sum sum.c sum_util.o
    
sum_util.o: sum_util.c sum_util.h
    gcc -c -o sum_util.o sum_util.c
    
test: sum
    ./sum 3 4
    
clean:
    -rm sum
    -rm sum_util.o
Simple: What do you think?
Sage: I don't know, what do you think?
Simon: I think we should try it and see.

Epilogue

And what do you think? Will it work? And why do an otter, mole and a water rat need to write software anyway?

Part of programming is how the code is built and used. Make allows the developer to go to this next level of instruction. Open source depends on it for developers to hand each other source code that can be reliably built and installed. It is also handy to make test cases that highlight difficult cases or error cases, so that a situation can be reproduced for consideration.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

author: burton rosenberg
created: 28 aug 2025
update: 3 sep 2025