# Building Code: make and Makefiles

## Today

- We'll continue working in the class 3 [assignment](https://classroom.github.com/a/gPR3HIWN).

## Some more on git

### Ignoring files

git is a version management tool, also known as "source control". This is an
important part: It is meant to keep track of the source files in your project,
not the generated / compiled binary code that is generated as your code is
built. The general rule is, if you used an editor to create a given file (like a
C++ source file, or a script), it should be added to the repository. If the file
was generated by running a command, it generally should not. In particular,
executables and object files should not be added -- they are big, change all the
time, are specific to the system that you are working on, and may well not
work for someone else (or you yourself) working on a different machine.

Ideally, compiled files etc should be put into a separate directory to avoid
confusion in the first place, and we'll get there. But in any case, one can tell
git that certain files should be "ignored", so that they aren't accidentally
being added. This is done by using a `.gitignore`
[file](https://git-scm.com/docs/gitignore). The `.gitignore` file itself should
be added to the repository.

### Pull requests

When working by yourself, you can just commit changes directly to the `main` branch of your
repository. Or, you might do work in a "feature" or "bug fix" branch locally, then merge it into the `main` branch. In order for changes to be reflected in your github repo, you push those latest changes in the `main` branch to github, using `git push <repo> <branch>`, or using the "Sync" button in VS Code. ("Sync" will not just push changes from your local repo to github, but also pull any changes from github to your local repo.)

However, when working in a team, it's often better to use "pull requests" to merge changes into the main branch. This allows for code review, discussion, and automated testing before changes are merged. Even when working alone, using pull requests can be a good practice to ensure that you review your own changes before they become part of the main codebase.

In order to create a pull request, you generally push your changes to a separate branch on github, then use the github web interface to create a pull request from that branch into the `main` branch. You can then review the changes, discuss them, and finally merge them into `main`.

I set up the Github classroom assignments in this class to automatically create a "Feedback" pull request for you once you push your changes back to github. You can use that pull request to document your work, and I can use it to provide feedback. Basically, we're using a general github feature (pull requests) to implement a simple workflow for submitting and reviewing homework assignments. The analogy between what happens in a classroom setting and in a real-world software development project only goes so far, though -- it maps well for submitting work and getting feedback, but in a real-world project, the goal in the end is to actually merge the pull request into the main code base, which wouldn't work well here, in particular since there will be many students working on the same assignment, doing similar things, and merging all of that into a single code base would be a mess of conflicts.

## Compiler Explorer

For anyone who's interested: [Compiler Explorer](https://godbolt.org/)

## make and Makefiles

The build script works perfectly fine, but for large projects it's not a good
solution, since even if we just make one little change in one file, the whole
project will be recompiled, which is slow. Instead we'll learn about using the
`make` command, which is the traditional way of building code, and still the
most commonly used build tool, though it may be hiding under additional layers.

You generally call `make <target>`, and the make command will then do whatever
is needed to create the `<target>` -- if it can figure out what to do. Normally,
one writes a file called `Makefile` in the current directory, which contains
rules that tells make what command to run to do the job of creating a target.
For example:

```
hello: hello.o factorial.o greeting.o
  gcc hello.o factorial.o greeting.o -o hello
```

The first thing, listed before the colon, is the name of the target. After the
colon follows a list of prerequisites, also called dependencies -- these need to
exist and be up-to-date before the target can be built. Finally, in the next
line is the command that will actually turn the prerequisites and create the
target. Note that there needs to be a TAB in front of the command, spaces won't
work.

So, generally:

```
TARGET: PREREQ1 PREREQ2 ...
  COMMAND
```

At this point, the Makefile knows how to create "hello" from the object (.o)
files, but we haven't told it how to make the object files, ie., by asking the
compiler to compile the source files:

```
hello: hello.o factorial.o greeting.o
  gcc hello.o factorial.o greeting.o -o hello

hello.o: hello.c
  gcc -c hello.c

greeting.o: greeting.c
  gcc -c greeting.c
[...]
```

#### Your turn

Write a `Makefile` like the above (it needs to be completed!), and run

```
[kai@mpro] $ make
[...]
```

Run make repeatedly and pay attention to what commands it does / does not rerun.
Edit a file (you can just make a trivial change, like adding a space or a
comment), and run make again. Observe which commands get executed.

Commit this step to your git repository.

### Variables

The main ingredient in Makefiles are rules like the above -- but we can also use
variables. For example, we can assign `gcc` to the variable `CC`, and then use
that instead, so that when you want to change to a different compiler later,
e.g., `clang`, you only have to change `gcc` -> `clang` in one place. `CC` is
the standad name for the C compiler, and we'll also use `CFLAGS` for additional
flags to the C compiler, like `-Wall`, which turns on lots of often useful
warnings. (For C++, the corresponding variables are `CXX` and `CXXFLAGS`.)

```
# for C
CC = gcc
CFLAGS = -Wall
# for C++
CXX = g++
CXXFLAGS = -Wall

hello: hello.o factorial.o greeting.o
  $(CC) hello.o factorial.o greeting.o -o hello

hello.o: hello.c
  $(CC) $(CFLAGS) -c hello.c

[...]
```

Variables are assigned using, e.g., `CC = gcc`, that means the variable `CC` now
has the value `gcc`. When using a variable, you have to put `$(...)` around it,
e.g., `$(CC)` will be replaced by `gcc` if that's the value we set previously.

### Special variables

There are a number of special variables that can be used in Makefile rules:

- `$@` target
- `$<` (first) prerequisite
- `$^` all prerequisites

We can use these so we don't have to put redundant information into the command
part of a rule:

```
CC = gcc
CFLAGS = -Wall

hello: hello.o factorial.o greeting.o
  $(CC) $^ -o $@

hello.o: hello.c
  $(CC) $(CFLAGS) -c $<

[...]
```

### Wildcard rules

Now we notice that the last three rules are pretty much the same: `something.o`
depends on `something.c`, and the command is also the same. Make allows wildcard
rules using `%` that we can use to write a general compile .c -> .o rule:

```
CC = gcc
CFLAGS = -Wall

hello: hello.o factorial.o greeting.o
  $(CC) $^ -o $@

%.o: %.c
  $(CC) $(CFLAGS) -c $<
```

As it turns out, make actually already knows a lot of standard rules, For
example, it also knows how to link multiple object files into an executable --
but we still need to list the prerequisites so that make knows what object files
to link together. So we can skip that rule. It also knows how to compile `.c`
files into `.o` files using the C compiler, or how to compile `.cpp` files into
`.o` files using the C++ compiler. rather short Makefile. Unfortunately, our C++
files have the .cxx extension which make does not know about by default, so we
have to add that rule when switching to C++ (or we could use the `.cpp`
extension).

```
CC = gcc
CFLAGS = -Wall

hello: hello.o factorial.o greeting.o

```

#### Your turn

Try out the simplified version of the Makefile above.

### Header files

As we've seen, C++ uses header files, just like C. Fortran77 on the other hand
doesn't have a concept of header files, and it doesn't need / want functions (or
subroutines) to be declared before using them. However, that means it's easily
possible to call a function with the wrong kind or the wrong number of
arguments, and the compiler is fine with that. Unfortunately, if you run the
code, it's highly unlikely that it works right, and fairly likely that it'll
just crash without giving much of a clue as to what's wrong.

Fortran90 (and C++20) supports "modules", which basically create something like
a header file automatically as they get compiled. This helps in ensuring that
one uses functions correctly. It does cause complications when using
make/Makefiles, though, and there are better tools out there (like cmake, as
we'll see.)

### Header file dependencies

Back to the `Makefile`: Things are pretty good already, but there's one problem:
make doesn't know anything about header files, unless we tell it about the
dependencies explicitly. Next time we'll see how some tools can help out with
that, but for now we're stuck listing dependencies on header files explicitly,
which is not so much fun...

```
CC = gcc
CLAGS = -Wall

hello: hello.o factorial.o greeting.o

hello.o: hello.h
greeting.o: hello.h
factorial.o: hello.h

```

## More on make, and some of its limitations

`make` and `Makefile`s are a standard tool that has been around for a long time
and can be found on pretty much any operating system. As often the case for
things that have been around for a while, there are different versions of the
make command around, and while the basic feature set is always the same and
compatible between versions, specific versions of make, like, e.g. the GNU make,
support additional features that might be useful, but if using those, one should
be aware that a non-GNU make might choke on such a Makefile.

As usual for these kind of basic tools, there is a wealth of information
available on the internet that goes into a lot more depth, for example the GNU
make [manual](http://www.gnu.org/software/make/manual/).

There are a number of limitations to the make utility, in particular its lack of
handling of dependencies on header files, and the fact that it cannot
automatically adapt to the specifics of a given machine. For example, I
hardcoded my compiler to be `gcc`, so that same Makefile wouldn't work on
another system where the compiler might be called `pgcc` or `clang`.

## Homework

- Finish the work from class 3.
- Finish the "your turn" exercises from above

  **Update**: It is okay to do the first "Your turn" (creating a Makefile and trying it), and then skip to the last "Your turn" (the simplified Makefile), though of course it won't hurt to go through the intermediate steps, too.
- Keep a log of what you've been doing and add any comments you might have.
- As you do your work, make commits to keep track of your work.
- Submit your work by syncing (pushing) your changes back to github. With VS
  Code, you can use the "Sync" button in the version management tab. On the
  command line, you can just say `git push`. 
- You can put your "report" bits directly into the "Feedback" pull request
  that'll be created once you push your changes back to github.

  Here is an example of how the "report" might look like: <https://github.com/UNH-HPC-2026/class-3-kai/pull/1>
