Why do you want to write a Makefile and not write a simple shell script? In the example that you consider simple, you make no use of any feature of make, you could even write a simple shell script that understands the keywords build
and clean
, and that's it!
You are actually questioning about the point of writing Makefiles instead of shell scripts, and I will address this in my answer.
Also note that in the simple case where we compile and link three moderately sized files, any approach is likely to be satisfying. I will therefore consider the general case but many benefits of using Makefiles are only important on larger projects. Once we learned the best tool which allows us to master complicated cases, we want to use it in simple cases as well.
The procedural paradigm of shell scripts is wrong for compilation-like jobs
Writing a Makefile is similar to writing a shell script with a slight change of perspective. In a shell script, we describe a procedural solution to a problem: we can start to describe the whole procedure in very abstract terms using undefined functions, and we refine this description until we reached the most elementary level of description, where a procedure is just a plain shell command. In a Makefile, we do not introduce any abstraction, but we focus on the files we want to produce and how we can produce them. This works well because in UNIX, everything is a file, therefore each treatment is accomplished by a program which reads its input data from input files, do some computation and write the results in some output files.
If we want to compute something complicated, we have to use a lot of input files which are treated by programs whose outputs are used as inputs to other programs, and so on until we have produced our final files containing our result. If we translate the plan to prepare our final file into a bunch of procedures in a shell script, then the current state of the processing is made implicit: the plan executor knows “where it is at” because it is executing a given procedure, which implicitly guarantees that such and such computations were already done, that is, that such and such intermediary files were already prepared. Now, which data describes “where the plan executor is at” ?
Innocuous observation The data which describes “where the plan executor is at” is precisely the set of intermediary files which were already prepared, and this is exactly the data which is made explicit when we write Makefiles.
This innocuous observation is actually the conceptual difference between shell scripts and Makefiles which explains all the advantages of Makefiles over shell scripts in compilation jobs and similar jobs. Of course, to fully appreciate these advantages, we have to write correct Makefiles, which might be hard for beginners.
Make makes it easy to continue an interrupted task where it was at
When we describe a compilation job with a Makefile, we can easily interrupt it and resume it later. This is a consequence of the innocuous observation. A similar effect can only be achieved with considerable efforts in a shell script.
Make makes it easy to work with several builds of a project
You observed that Makefiles will clutter the source tree with object files. But Makefiles can actually be parametrised to store these object files in a dedicated directory, and advanced Makefiles allow us to have simultaneously several directories containing several builds of a project with distinct options. (For instance, with distinct features enabled, or debug versions, etc.) This is also consequence of the innocuous observation that Makefiles are actually articulated around the set of intermediary files.
Make makes it easy to parallelise builds
We can easily build a program in parallel since this is a standard function of many versions of make
. This is also consequence of the innocuous observation: because “where the plan executor is at” is an explicit data in a Makefile, it is possible for make
to reason about it. Achieving a similar effect in a shell script would require a great effort.
Makefiles are easily extensible
Because of the special perspective — that is, as another consequence of the innocuous observation — used to write Makefiles, we can easily extend them. For instance, if we decide that all our database I/O boilerplate code should be written by an automatic tool, we just have to write in the Makefile which files should the automatic tool use as inputs to write the boilerplate code. Nothing less, nothing more. And we can add this description pretty much where we like, make
will get it anyway. Doing such an extension in a shell script build would be harder than necessary.
This extensibility ease is a great incentive for Makefile code reuse.