
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.
makemake to build programs. What's that about?
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.
make to automate the process.
make figures out what is out of date and recompiles only what is necessary.
make.
make?
make which target to build.
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.
make target
makesum.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 ;
}
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.
# a simple Makefile for a simple program by a simple programmer
sum: sum.c
gcc -o sum sum.c
make sum to
build my program. But what if I change sum.c, will it rebuild?
sum and sum.c will be compared.
If you change sum.c, the time stamp is updated some make knows to
rebuild sum.
sum, not just its building. Like, getting
ready to archive the program or an example use to make sure the build went well?
clean
target with no dependencies, that way it is always out of date and therefore it always
runs; or a test target.
# 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
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 ;
}
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.
sum_util.c but it said
ld: Undefined symbols:
_main, referenced from:
I really am not sure what I should do with this.
-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.
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?
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?
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
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.

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