pete > courses > CS 315 Fall 26 > lab 01: make and git
Lab 01: make and git
Goals
- use make to streamline compilation
- set up and configure git to work with your partner
recall the compile command we used this morning:
$ gcc -Wall -pedantic -o copy-file copy-file.c
this is annoying to type every time you want to compile
the make program allows us to codify the commands used to compile programs in a single configuration file and then just run make to do everything necessary
this configuration file must be in the current directory, named Makefile (Linux is case sensitive, meaning that Makefile is different from makefile, and while both work, convention dictates the former)
here is the Makefile for today’s program:
copy-file: copy-file.c
gcc -Wall -pedantic -o copy-file copy-file.c
.PHONY: clean
clean:
rm -f copy-file
let’s walk through it step by step
a Makefile is composed of recipes
for now, let’s look only at the first one:
copy-file: copy-file.c
gcc -Wall -pedantic -o copy-file copy-file.c
each recipe has three parts: a target (the thing to be built), a list of prerequisites (the things required to build the target), and the commands that do the job
the first thing in every rule is the target: it’s the thing that comes before the colon—in this case, copy-file
this should be the name of the file that gets produced
a recipe’s prerequisites are listed after the colon
thus, the copy-file recipe has a single prerequisite: a file named copy-file.c
finally, on the following lines, indented, are the command(s) that build the target
in this case, to build copy-file, we use the gcc command from above
we’ll get back to other recipe in a moment, but first let’s test this one
back in the shell:
$ make make: 'copy-file' is up to date.
(if you run make all alone, it will attempt to build only the first target in the file: in this case, copy-file)
what has happened here is that make has examined the modification times of the prerequisite (copy-file.c) and the target (copy-file) and has deduced that the former has not changed since the latter was built, and therefore there’s no point in compiling again
this isn’t so useful in an example with a single source file, but if we’re building a project with hundreds or thousands of sources files, being able to detect and skip unnecessary compilation can save us a bunch of time
we can use the touch command to update the modification time of the source file to the current time:
$ touch copy-file.c
and then use ls to observe that it is now more recent than the program:
$ ls -ltr -rw-r--r-- 1 pete pete 6039 Sep 11 22:10 notes.md -rw-r--r-- 1 pete pete 219 Sep 12 08:47 copy-file-design.c -rw-r--r-- 1 pete pete 109 Sep 12 08:48 Makefile -rwxr-xr-x 1 pete pete 16984 Sep 12 09:08 copy-file -rw-r--r-- 1 pete pete 1215 Sep 12 09:51 copy-file.c
and now running make will actually cause work to get done:
$ make gcc -Wall -pedantic -o copy-file copy-file.c
(note that make prints out the command that it runs, and also re-note that gcc produces no output when it works)
check out the other recipe:
.PHONY: clean
clean:
rm -f copy-file
by convention, Makefiles include a rule to clean up the workspace: to delete (remove) all files that can be produced from other files
in this case, it means the program, which can be built from the underlying source file
(more on this later when we get to git)
for now, though, note two things about this rule:
- it does not have any prerequisites
- it does not actually produce anything, and thus the target is kind of a misnomer
to recognize this fact, we tell make that this is a phony rule—that is, that it does not actually produce anything
the concrete effect of doing this is that make will still run the rule even if a file named clean exists
even if you don’t imagine such a file ever creeping into your source directory, it’s still a good idea to mark the rule phony to be safe
recall from before that, by default make only builds the first target in the file
if we want it to "build" the clean target, we give that as a parameter on the command-line:
$ make clean rm -f copy-file
again note that it prints out the command it executes
and also note that the compiled program has disappeared:
$ ls copy-file.c copy-file-design.c Makefile notes.md
that’s enough about make for now; we’ll add more complexity next week
now, git
git is a version control system (VCS) or source-code management (SCM)
it performs as advertised: it helps manage your source code, it helps control the versions of your source code
the basic idea is that is tracks checkpoints in your development process
add a feature, make a checkpoint
fix a bug, make a checkpoint
then git will allow you to examine these various checkpoints, compare them, etc
this makes it easy to roll back to a previous version if, for example, you discover a bug
all git commands follow the pattern
$ git command command-arguments
in git parlance, a repository is a single collection of source code
in this class, you will create one repository per assignment, and that repo will contain all source files necessary to build and run it
the init command creates a repo
"." refers to the current directory, thus the following sequence of commands creates a new directory called sample-repo, moves into it, and creates a new repo inside
$ mkdir sample-repo $ cd sample-repo $ git init . Initialized empty Git repository in /home/pete/tmp/sample-repo/.git/
ALTERNATIVELY, git can run mkdir for you:
$ git init sample-repo Initialized empty Git repository in /home/pete/tmp/sample-repo/.git/ $ cd sample-repo
(do NOT run both sets of commands in this section, as they do the equivalent thing!)
when you create your first repo, you will be met with a message like this:
hint: Using 'master' as the name for the initial branch. This default branch name hint: is subject to change. To configure the initial branch name to use in all hint: of your new repositories, which will suppress this warning, call: hint: hint: git config --global init.defaultBranch <name> hint: hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and hint: 'development'. The just-created branch can be renamed via this command: hint: hint: git branch -m <name>
I’m not going to inflict the notion of branches on you (yet) but know that you can pursue different development ideas within the same repository and keep them separate by putting those different ideas in different branches
historically, the primary branch of development has been called "master"
this message tells you how to change the name of the default branch for any repositories you create in the future
to change that default name (for example, to "main"), use this command:
$ git config --global init.defaultBranch main
(if you don’t pick a default using the above command, git will show you this warning every time you create a new repository with git init)
now we can create some files:
$ ls copy-file.c Makefile
and ask git what it thinks of its current situation:
$ git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
Makefile
copy-file.c
nothing added to commit but untracked files present (use "git add" to track)
this tells us that it found two files that it doesn’t know anything about: they are untracked
to ask git to begin to track changes to these files, we must "add" them to the repo
$ git add copy-file.c Makefile
and then we can check status again:
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: Makefile
new file: copy-file.c
and now they are ready to be "committed", which means that git will save a new revision in its history that contains the exact contents of these files at this instant, so we can roll back to them if necessary
to commit these files, we use the "commit" command, which requires a message describing the purpose of the commit (choosing a good commit message will make it easier to dig through your revision history later on):
$ git commit -m 'initial commit'
but it doesn’t like it:
Author identity unknown *** Please tell me who you are. Run git config --global user.email "you@example.com" git config --global user.name "Your Name" to set your account's default identity. Omit --global to set the identity only in this repository. fatal: empty ident name (for <user@host>) not allowed
this is because git needs to record who is responsible for these changes
we can make git happy by running the commands it tells us to run:
$ git config --global user.email "pete@middlebury.edu" $ git config --global user.name "Peter Johnson"
(You only need to do this once ever: because it uses --global, future commits and future repositories will use these configuration parameters)
now we run the same command again and it works:
$ git commit -m 'initial commit' [master (root-commit) 7ed4973] initial commit 2 files changed, 67 insertions(+) create mode 100644 Makefile create mode 100644 copy-file.c
now let’s make a change: I’m going to change the value of BUFFER_SIZE to 20000
first, "git status" will show us that git has noticed we’ve changed a file:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: copy-file.c
no changes added to commit (use "git add" and/or "git commit -a")
and we can then ask git to show us the precise differences between the "working copy" (that is, the current uncommitted version) and the most recent committed version using the "diff" command:
$ git diff diff --git a/copy-file.c b/copy-file.c index 95c63d3..89863d6 100644 --- a/copy-file.c +++ b/copy-file.c @@ -7,7 +7,7 @@ #include <stdio.h> #include <stdlib.h> -#define BUFFER_SIZE 10240 +#define BUFFER_SIZE 20000 int main(int argc, char *argv[])
lines preceded by a "+" indicate code that has been added
lines preceded by a "-" indicate code that has been removed
to commit these changes, we first need to "add" them to the list of files to be committed:
$ git add copy-file.c
which is corroborated by "git status"
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: copy-file.c
and now we can commit:
$ git commit -m 'copy more bytes at a time' [master 9b6ecc4] copy more bytes at a time 1 file changed, 1 insertion(+), 1 deletion(-)
finally, we can ask git about the version history using the "log" command:
$ git log
commit 9b6ecc46160c89840a09b7c74c53dfbd56e83db5 (HEAD -> master)
Author: Peter Johnson <pete@middlebury.edu>
Date: Wed Sep 11 19:31:54 2019 -0400
copy more bytes at a time
commit 7ed4973818150ebcca8cec7223975b002cf717f3
Author: Peter Johnson <pete@middlebury.edu>
Date: Wed Sep 11 19:26:37 2019 -0400
initial commit
your basic (working alone) workflow for git should be
- get your code in a stable state (finish adding the feature, finish fixing the bug, etc)
- run "git status" to verify that only files pertaining to that feature or bug have been changed
- run "git diff" to verify that the only changes within those files pertain to the feature or bug
- run "git add [filenames]" to stage the files for commit
- run "git commit -m 'informative commit message'" to actually commit the files to the repository
this is helpful when working in isolation, but tons of code development these days happens in teams, sometimes distributed around the world
we’re not going to worry about such large-scale organization (though supporting that kind of scale is exactly why git was invented); we’re going to focus on sharing code between partners
the first step is that each partner is going to have a git repository on their own computer, in which they do development
the complication then arises when one partner makes a commit or three and wants to send those commits to the other partner
the good news is that git includes facilities to send all commits from one repository to another
the problem is: how do we identify where to send those commits?
laptops (upon which much—and perhaps even most—development happens) can move around the Internet a lot, which makes it difficult to identify where to send those commits
the solution is to have a stable, easily-findable computer sitting on the Internet somewhere
when Partner A has commits they want to share, they push the code from their laptop to this stable machine on the Internet
Partner A tells Partner B that there is new code available
Partner B pulls the new commits from the stable machine
many people doing development in the real world use github.com as the stable machine on the Internet; in this course, we will use a Middlebury-managed machine named weathertop
therefore, between you and your partner, for each assignment, you will have four repositores:
one remote repository for you, to which you will push and your partner will pull
one remote repository for your partner, to which they will push and you will pull
one local repository for you, in which you will edit code, add, and commit, and push to your remote repository
one local repository for your partner, in which they will edit code, add, and commit, and push to their remote repository
so how do we set all this up?
it will happen in a bit of a weird order (though this weird order ends up being more concise)
each partner will:
- log into weathertop and create their remote repo on that machine
- on their laptop, create the local repo by cloning the remote repo on weathertop
- on their laptop, tell the local repo about the other partner’s remote repo, from which code will be pulled in the future
because you’ll use this process several times throughout the semester, I’ve put all the instruction on a separate page, a link to which you can find in the Resources section of the main course website
also here: git
as with make, we’ll add more complex commands as the semester proceeds, and introduce some shortcuts, but for now that should do you