Tired of typing endless compile commands? Join me in this fun, hands-on guide to mastering GNU Make! We’ll walk through creating a Makefile to simplify your coding builds, covering C++, C, and assembly projects. Learn how to set up variables, define functions, manage dependencies, and organize build directories to cut compile times drastically. From practical tips like using clean targets to advanced tricks like dynamic variable assignments, this video has it all. Whether you’re a beginner or a seasoned coder, you’ll see how Makefiles can transform your workflow. Plus, I share a real story of turning a 1-hour build into 1 minute! Subscribe for more coding tutorials, and let me know how Makefiles change your game. Scan the QR code for more resources!
Introduction to Make Files 00:00:00
What is a Build System 00:00:11
Problems with Manual Compilation 00:00:16
Using Scripts vs Build Systems 00:00:36
Benefits of Make Build System 00:00:56
Personal Experience with Build Times 00:01:15
Creating Sample Source Files 00:02:20
Installing Make Build System 00:04:56
Creating a Makefile 00:06:02
Defining Functions in Makefile 00:06:46
Calling Functions and Variables 00:07:13
Setting Up Compiler Variables 00:10:51
Dynamic vs Static Variable Assignment 00:11:56
Compiler and Linker Flags 00:12:44
Setting Up Build Paths 00:19:16
Printing Variables for Debugging 00:21:24
Understanding Targets in Make 00:25:10
Dependency Graph and Compilation 01:02:04
Managing Build Directory 01:03:11
Creating Binary Target 01:05:44
Handling Object Files 01:11:29
Running the Program 01:13:16
Clean Target for Cleanup 01:17:00
Advanced Build System Features 01:22:46
Git Ignore for Build Files 01:24:34
Conclusion and Call to Action 01:25:08
Thanks for watching!
Find us on other social media here:
- https://www.NeuralLantern.com/social
Please help support us!
- Subscribing + Sharing on Social Media
- Leaving a comment or suggestion
- Subscribing to our Blog
- Watching the main “pinned” video of this channel for offers and extras
Hey there, let’s talk about using make files and the make build system.
So what’s a build system? It’s basically just a program that helps you compile your program.
In a previous video, I talked about the idea that it’s really cumbersome
to type out build commands, you know, compiling commands, linking commands and so forth for your
cumbersome and you’ll forget everything that you’re supposed to be doing. So the next upgrade
from that was in a previous video I talked about let’s take all the commands and put them into a
script so that we don’t really have to remember all the commands. We can just, you know, create
a build script and then just kind of run it when we want a program to compile and link and execute.
But then the thing is about a script is if you have a very, very large program,
it might take a very long time to compile. So a build system will take care of that for you.
care of that for you the build system that i’m about to show you the make build system
it can carefully calculate which source code files you’ve actually changed since the last
time that you built your program and it will only rebuild the source code files
that actually need to be rebuilt you know into their corresponding object files and such
so for as a personal example long time ago before i learned how to use build systems i had a giant
And every single little change that I made, I would just have to wait for like an hour.
Like I’d have to go get a sandwich or go do something else or work on a different project for an hour.
Then come back and see if my changes actually affected anything.
Somewhat quickly, I got irritated enough that I looked into build systems.
And I realized that make, the one that I’m showing you today, is pretty easy.
It goes really, really far, but it’s pretty easy to get started.
And it drastically reduced my compile times.
set up, you know, the make file and everything so that I let the build system do the building.
My build only took like a minute. So I could do like a quick change and then just wait one minute
to see the results and then another quick change and so forth. Okay. So if you look at the screen
here, I’m sitting here on a blank desktop. Hopefully I’m recording everything. Am I?
I’m sitting on a blank page here. So let me just make a couple of source code files. This is not
This is not a tutorial for C++ or assembly or anything.
I’m just going to copy paste a couple of sample programs in here real fast.
So I’m creating a C++ source code file.
I’m calling it first.cpp and it’s going to call functions from other source code files.
This is going to be a hybrid program.
Again, don’t really worry about this.
My other videos already explained hybrid programs and so forth.
I’m just going to show you how to use the make build system.
the make build system.
So let’s make another source code file called second dot C.
And then another one called third dot ASM.
So I’m linking a I’m linking three source.
Whoops, that’s wrong.
I’m linking three source code files.
And they’re all in different languages just to prove to you that this will work
with a hybrid program.
And so you can kind of see I’ve got first, second and third.
The first one is C++.
The second one is just C.
Third one is assembly.
If I look at it real fast, get ready to pause the video.
real fast get ready to pause the video then you can see that it’s C++ and if we look at the second
one it’s just C and if we look at the third one it’s just a very simple assembly program and they
just sort of say hello and that’s it okay so you know at this point in my other video I would have
made a compile script I’d say nano compile and then I’d stick a bunch of compile commands inside
of it let’s see do I even have that from last time let me see if I got it if I have it I’ll
I got it if I have it I’ll just paste it real fast just to show you compile I don’t think I’ve
got it although I do okay I’m just gonna paste it in here and it’s not the point of this video check
my other video if you want to kind of understand everything that’s going on in here but it’s just
a script so I’m gonna go compile clear and then list and then in the last video I just said let’s
and it compiles everything and it links the binary and then I can run main which is my binary and then
it just runs this program so it’s no problem it seems okay but again imagine that your program is
huge many many lines of source code in every single file and many many many source code files
and perhaps you’re pulling in libraries and shared objects and just like you know a bunch of other
stuff your your your building could slow down drastically so I’m going to remove the compiler
the compiler script and then I’m going to manually remove all the compiled
object files. Got to be careful with this. Remove the binary. Okay. So now I’ve got
that one, two, three. Okay. Now I’m ready to set up a make file. So I guess first
you probably should have the make build system installed. If you’re if you’re
learning about C++ and assembly you probably already do but just just so you
know it’s a it’s gonna show up in Ubuntu as the build essentials package so
So sudo apt install build essentials.
I can’t remember if this is plural or not.
It’s either build essentials or build essential.
You can do an apt search or a DNF search depending on your system.
We can do build essential and see what shows up.
So I’m just going to go to the top build essential.
Okay, so it was singular, not plural.
I have it installed already.
Let’s see.
If you just want to install make by itself, you can probably do apt search.
search, just make and go up to the Ms.
You can see right here.
It’s just pseudo apt install.
Make is all you really have to do to get the whole make build system.
They’ve got a website, you know, search for Gnu make.
I’m going to type it on the screen here.
Gnu make search for that.
You’ll get a nice website with a bunch
of documentation and examples that go way beyond this video.
But I’m just going to show you the basics so you can get started.
So the first thing you should do is create a file called make file.
create a file called make file. I think under lowercase m is supported but I usually do uppercase
m which is obviously supported because I always do it and what you’re really going to do is you’re
just going to create an empty file call it make file and then inside of the make file you’re just
going to give make a bunch of commands and things that you want it to do and then later when you’re
actually ready to compile you’ll just type make as a command and then give make an instruction and
have lots of instructions and targets available inside of the make file for
make to grab onto. Okay, so moving on. The first thing I want to show you is how to
define a function in make. You probably won’t do this a lot, but for me I kind of
like to print, you know, pretty messages and sort of like do repetitive tasks in a
function. So when you want to define a function you just use the keyword define
and then you name the function. So I’m gonna make a function called say which I
see what it’s for I know it’s silly define say and then I’m going to tab here and then I’m going to
tell it to print some information I’ll just put test info for now the thing about calling functions
is you usually put a dollar sign and then an open parenthesis and then the name of the function and
then after that arguments to the function so here you can see actually let me get line numbers up
here on nano you can see online too I am calling a function called info it’s a built-in system
it’s a built-in system function you don’t have to define it yourself and I’m
telling it I want you to print some info to the screen and I want you to print
test info to the screen and that’s it so in my make file I can just call that
right away with call you know dollar sign parentheses and then the keyword
call and then name the function that I want to call closing parentheses and
then if the function takes arguments I put a comma and then just start
providing the argument so I’ll just say hello what’s up actually this shouldn’t
up. Actually, this shouldn’t take my arguments right now. We’ll upgrade it in a second. But
so this is a very simple makefile. It doesn’t actually have any targets. It will just simply
do things. This kind of stuff is useful. At the very top of your makefile later, we’re going to
define like a bunch of variables. And sometimes it’s nice to print them out. So I just want to
show you how to start printing so that maybe if your makefile isn’t working, you’ll realize that,
oh, that variable is wrong because you printed everything when it started up. You don’t have to
You don’t have to do it that way.
I just like to.
So how do you invoke the make file?
We just literally type make.
And because we named the make file, make file,
the make program will automatically figure out
where it is and go find it and load it.
But you can specify the name of a make file
if you really want to.
I think it’s the dash F switch.
Actually, let me look it up just to be sure.
I’m gonna go man make and just go down a second here.
Yeah.
So this switch right here,
here dash f and then specify the file name or file path or dash dash file and then specify the name
or path that’s how you could use a non-standard make file but almost no one does that everyone
just likes to to name it this way and then just type make so if i type make notice how the first
thing it does is it says test info which is what i told it to do in that function which i called
and then it says no targets gotta stop because we didn’t add any targets yet okay by the way if
about what I’m even doing this for like compiling and whatever go see my other
videos where we talk about compiling and linking and all that stuff this is just
to make things a lot more easy and powerful so let’s upgrade this save
function and I’m just gonna put some brackets and I’m gonna say my super cool
make how about like builder I guess and then instead of test info I’m gonna put
dollar sign one which basically is gonna tell this function you know when I call
say, then the dollar sign one variable is going to show up as the first argument that I provided.
So now when I call say, it should have hello, what’s up as the first argument that goes into
the function. So that means it should print my super cool builder and then the message that I
had it print. One more time, notice how it says my super cool builder. Hello, what’s up, I just like
to do it this way, because then it’s easier for me to differentiate messages that the build system
itself from the actual make file and messages that maybe the tools I’m calling on are giving like GCC or whatever
So let’s edit this a little bit more
The second thing I would like to do is well, maybe I’ll change this to just you know now running or something
Now let’s set up some variables. I’m gonna do like a little thing here. I’m gonna say variables
Put a bunch of comments in make files comments are just hashes
So, you know, this, these lines right here don’t really do anything, 9, 10, 11.
And I’m going to start setting up some variables for my compilers.
So again, actually compiling is in a different video.
We assume right now that you already know how to do it.
So I’m going to say, let’s do some variables for compilers.
You don’t have to do it this way, but the standard convention for naming a regular C
compilers or GCC seems to be just CC as the variable name.
So I’m saying a variable named CC is going to be equal to GCC.
to be equal to GCC and then from this point forward every time I use the variable CC in my
script in my makefile it’s actually going to be like typing GCC and you know if you don’t understand
why you would use variables you probably need to learn how to code a little bit more but basically
it makes everything more powerful instead of typing GCC everywhere and then maybe later if I
want to change it I have to go change it in a million places now I can just change it in one
place and so it’s more powerful let’s do the same thing for the C++ compiler I’m going to say G++
compiler I’m gonna say G++ same thing for my Yasm assembler I’m gonna say
we’re using Yasm also notice how when I assign variables I’m not just using an
equality or an assignment operator I’m not just doing equals that is a little
bit different of a way to assign you can do that but if you just put an
assignment operator by itself just like the equal then if you later change
depends on on the right side, like another variable, then it will update on the left
side. So it’s like, it’s not really a permanent assignment. It’s more like a dynamic assignment.
So, you know, if you want to make sure that the variable has a fixed value that doesn’t change,
even if you start changing variables that it depends on, then use colon equal. I’ll just leave it at that for now.
Not super important. You can also tab and, you know, kind of line things up and it’s no problem.
just to make things look nicer.
So now let’s set up some flags for C and C++ and YASM.
So I’m going to create a variable called ccflags,
do the same thing again where I do the colon assignment operator.
I’m just going to give it some flags.
I want it to show me all warnings,
and I want it to be pedantic about its warnings,
and I want it to generate debug symbols.
build system we want to try to compile source code files into object files first
and then at a different stage link the object files together we we don’t want
to just compile everything in one fell swoop directly to a binary in one line
because we wouldn’t really be leveraging the power of the build system in a
moment I’m going to draw a diagram that tries to make this a little more clear
for now I’m just typing up variables so just bear with me when I’m you know
Cpp flags.
So for C++, I’m actually going to reuse a variable here.
I’m going to reuse the CC flags because all the flags
above are fine for C++ as well as C.
So that means like in my C++ compilation,
I want all warnings and I want it to be pedantic and I want to generate debug
symbols, but in C++, we’ll specify the C++ standard.
So C++ 23.
kind of already leveraging variables against other variables to save typing and make things a little
bit more module. If I decided that all my C was not going to show warnings or was going to convert
all warnings into errors or, you know, use a different flag or something, all I’d have to do
is change it in one spot, line 20 and line 21 would automatically get updated. Also keep in mind
because like I said before, we have this colon assignment operator here going on the right side.
value of cc flags then it would be reflected if i didn’t use um the assignment operator you know
what let’s let’s just do it let’s just do it real fast i’m going to say call say i’m going to say
let’s let’s see cpp flags is and then i’m just going to print cpp flags you print variables kind
of the same way that you call functions you just put a dollar sign and then surround the variable
name in a set of parentheses and that’s pretty much it so i’m going to copy this line a couple
And in between CPP flags, actually let’s do CC flags.
We’ll assign CC flags to something.
I’ll say hello test.
And right now the way CPP flags is written, the assignment kind of sticks.
It’s like permanent fixed.
It doesn’t dynamically depend on CC flags.
So we should see two instances of the exact same message.
Let me open up another window here because I want to keep that editor open.
because I don’t I want to keep that editor open so we’ll do a make and notice how when it prints
the CPP flags they’re the same it’s the same both times even though I changed CC flags in between
those two messages on the other hand if I don’t put that to colon if I just put a sign then now
it is dynamic notice how the second message uses the modified version of cc flags so keep that in
So keep that in mind if you want a variable to be able to change anytime you depend on
anytime a dependent variable that it depends on changes you’ll assign one way and if you
don’t want it to change you’ll assign it another way okay so that’s enough of that I’m going to
take away that message and I’m going to change this back to non-dynamic okay then we’ll do the
same thing for assembly assembly flags this is going to be the Yasm compiler I’m going to tell
I’m going to tell it that I want ELF 64.
I want a 64 bit executable.
I want to dwarf to debug symbols.
I think in Yasm you have to specify the type of symbol.
I think I can’t remember if you actually need to.
So you could try just, you know, dash G if you want.
It may or may not work.
But I always just like to specify at least for assembly.
So then let’s do some linker flags.
Okay, so link flags, you know, what is the linking stage?
What’s going to happen in a hybrid program?
You know, for me, it’s just going to be the G plus plus GCC linker.
So that means I’m going to do all the CPP flags because I want wall and pedantic and
debug symbols and I want the C plus plus standard set and all that stuff.
Whatever you want to do is fine.
And then I want to just specify.
I don’t think you have to do this on modern systems, but I’m going to specify I want to link a 64-bit executable
And then I have to specify no pi
Which I think is kind of an Ubuntu thing your program probably won’t run if you don’t do this
So just trust me for now put no pi in the linking stage and then we’ll do another flag called no exec stack
Which is also a somewhat new thing which prevents
stack which means you might be less hackable not sure if it’s how safe it really is but you know
probably improves the safety a little bit uh let’s see at the last minute okay so now uh
now that i’ve set up the link flags uh i need to like add on the dash c flag for the actual
compiler flags because uh you know if if you know this from my previous videos if you’re going to
compile to an object file and not directly link everything right away you need to add the dash c
away you need to add the dash c flag to let the compiler know oh i just want to compile to an
object file and then later i’ll come along and link so i’m going to go cpp flags is equal to
and i’m going to say equal to itself and then just add a dash c at the end of it and then we’ll do
cc flags and then it’s going to equal to itself with just a dash c at the end of it so
I had CPP flags on line 21 equal to just this basic thing.
And then I reused CPP flags for the link flags,
which means if the link flags didn’t use that colon,
I would basically have ruined the link flags variable by the time I went down
here and changed it. So you don’t have to organize your make file this way.
You can actually hard code a lot more stuff into the variables.
I just like to try to be more dynamic. It’s more fun.
up all the basic variables for the compiler let’s set up some variables for paths so i’m
going to set up paths here so what do i mean by paths you know this is a build system it’s got to
know where things are right so for starters you probably want to name your binary you know the
executable file so i’m just going to make mine main you can make anything you want like my program or
whatever but you know for mine it’s going to be main and then i’m going to set up another variable
going to set up another variable that depends on it so I’m going to say the actual binary path is
just going to be you know this main meaning the current directory and main also you know most it
seems like most make files have it set up so that all of the intermediate files like the the object
files and the binary they get built in the current source directory you know like the root of the
Sometimes also I like to just make a build directory.
So it’s really, really easy to just kind of clean everything out.
You just delete the build directory and you don’t have to worry about the repository getting
super cluttered with object files in the meantime.
So I’m going to name a build directory here.
I’m going to say build directory name is build.
And then so you can imagine there’s going to be a folder here now named build that shows
up whenever, well, I’m going to add more targets later when I actually start building.
when I actually start building, we’re still just naming things.
So then I’m going to name the actual build directory as the current directory slash and then build dir name.
Okay.
So that means the bin shouldn’t just be in the current directory.
It should be in the build dir and then a slash.
And then instead of typing main, actually, I should probably reuse that variable.
So let’s do bin name.
And again, reusing variables is really, really good because it just saves you work.
I can change things in less places when I leverage variables more.
If I had the word main written down all over the program, I’d have to change that in every
single spot of the make file if I wanted to change main to something else.
But in this case, I only have to change it in one spot.
Okay, so now maybe it’s probably a good idea at this point to start printing out what we’ve
done.
So I’m going to do a bunch of, you know, calls to print things.
you know calls to print things print everything out just for double checking ourselves
okay how can i do that i’m going to do that with a call uh the uh say you could also just do uh info
and then you know something like that like we did up above in the save function if you want a less
fancier build system but i like doing it this way so i’m going to say what um let’s do cc uh
CC and I’ll just put maybe like a quality operator or something and then
I’ll just print out what I put for CC and I’ll just start duplicating this
line a few times and I’ll say I’ll just print out everything that I added as a
variable CPP and then ASM type ASM and this just kind of you know it just kind
typed something in incorrectly might take me a while to figure that out but if
I print it all out at the beginning of the of the make file running that I can
just visually inspect what the variables actually contain so let’s do CPP flags
and then we’ll do CC flags
SM flags.
And then we’ll do one more.
It’s going to be probably, oh, link flags.
There we go.
So link flags.
Bringing out a lot of stuff here.
Then I can print the directories, I guess.
So maybe I’ll duplicate this a few more times.
I want to do builder.
The build directory, just so I know where it is.
and I can verify that it’s like a real path.
Builder, maybe I’ll duplicate this one more time
so that I can see the absolute path
to the build directory.
Just for my own information,
because sometimes if you get it wrong,
this is another way to kind of see what’s going on.
So there’s a function in make that’s built in.
You pretty much just say dollar
and the name of the function is abspath.
path and whatever you put inside of it it’ll try to give you the absolute path of that thing
so i’m just telling it now that i want to print the absolute path of the build directory
by the way um you probably don’t want to convert all of your variables to absolute paths because
make has a huge problem with spaces in paths and file names so keep it relative unless you’re just
going to print something um you’ll find out the hard way i mean feel free to try it and prove to
yourself it’s not a good idea um but i found it the hard way many times because i kept forgetting
I kept forgetting. So then we’ll just do the binary, I guess. Okay. Yeah. We’ll do one more
for the binary. It’s going to go there and maybe I’ll duplicate this so that I can say
what the absolute path is. Okay. Abs path. And then if I screw this up, I’ll just look up the
I’m going to do clear and make so I can just see what it’s going to print.
And let me just double check here.
Okay.
So everything looks good there.
Looks good there.
Looks pretty good.
Absolute path to the builder is my stuff.
All right.
Okay.
So seems good so far.
I’m just going to leave that in there because it’ll be easy to double check it.
And now we’re going to do targets.
So what is the target in the make build system?
in the make build system a target is just something that you might want to create or or
or build or compile or something targets are not only for uh building uh executables or object
files or you know something that you make targets sometimes can just be called they’re called phony
targets but you can just call a target and it will execute a bunch of commands within that target
even though it’s not building something specific that is needed so you can use make files to do a
really powerful stuff. Let me show you first off, we’ll do a we’ll do a target. I’ve already
prepared something up here. I’m just gonna I’m just gonna copy paste it here because it’s going
to take a long time to type if I don’t. So here’s a target. An example of a target is okay, I’m going
to name the target the target menu and I’m gonna put a colon. So now later I can type make menu
and what will happen is it’ll execute all those commands that are listed below. Each command
is really just going to be a shell command.
You have to use tab to indent for the command itself.
Notice how I have a bunch of echo statements
and then I’m just like printing stuff, right?
So this whole target, it doesn’t actually build anything,
but it will print a menu to the screen for me,
which is pretty convenient.
When you’re not actually building something,
like you’re not building the executable
that the target doesn’t name an executable,
then it’s called a phony target.
And you want to name it as a phony target
phony target with this line 67 down here because if you don’t the build system might actually
eventually end up getting confused between a fake target and something that might be sitting on your
file system like if i accidentally put a file named menu on my file system at the root of my
repository then make might get confused and think well i see the file so therefore i don’t need to
build it and then the menu will never show up again so this way i’m saying this is a phony target
at the file system it’ll just always execute that target for me so this is the basics of a target
you just name a target put a colon and then every line under that is a shell command that you wanted
to execute so let me just show you that real fast we save to be sure so we’re going to do clear and
actually the absolute paths are starting to bother me let me get rid of them right now
head before I made this video. Okay, and we can just sort of trim the fat a little bit. So I’m
going to do clear and make. And notice how the menu target just ran by itself already.
The convenient thing about make files is the first target that shows up is going to be known
as the default target and it will just get executed automatically if you don’t specify
what to execute. So notice how I just typed make by itself and I didn’t actually specify a target.
I could specify make menu and the same exact thing happens.
Notice how I said make menu, the menu shows up again.
But if you don’t specify anything,
then it’ll always execute the first found target.
And just to prove that to you right here,
I’ll do maybe I’ll do like a different target
and I’ll just call it hello.
And I’ll just have it echo hello.
So now if we just execute make with no arguments,
notice how it just prints hello.
if I want the menu again, I got to go make menu.
Right? Okay. So I’m going to, I’m going to set the menu back to the default target.
Another thing that sometimes people do is they’ll just make a target called default.
I guess I’ll do that here, make a target called default. And they’ll say that the
default target depends on whatever they want to actually run. And you don’t even need to
call it default. You can call it hello or anything that you want. So, you know,
put to lead default just so you know that this is not a standard system name that’s expected so
the default target it’s just the name of the target then a colon and instead of putting shell
commands underneath i could put dependencies on the right so now i’m saying that the default target
depends on the menu target which means in the make build system first what it’s going to do is it’s
going to go check to make sure that main or sorry menu is satisfied because menu is actually a fake
Because menu is actually a fake, you know, a phony target, it’s always going to get executed.
And then when it’s finished and laDefault realizes, okay, all of my dependencies are satisfied now,
then laDefault is going to execute and it’s actually going to end up doing nothing.
I mean, we could put some commands under here if we want.
I’ll put the word nothing.
And just to prove to you that menu will get executed first because it’s a dependency of laDefault.
And then finally laDefault will execute its commands and print the word nothing.
execute its commands and print the word nothing.
Let’s do that and then I’ll erase the echo command there.
So we’ll do clear and make.
Notice how the menu got printed first
because it was a dependency of the default
and the default is the default target.
And then after the dependencies were satisfied,
then the default did its thing and printed nothing.
But so let’s get rid of that.
And now it’s just like when you do make with no arguments,
then the menu gets printed.
printed. Okay, so what else can I talk about real fast here? Let’s talk about
silent commands. I’ll put a little comment menu. Let’s talk about silent
commands. You see all these at symbols here? There are a bunch of symbols that
you can use throughout a make file. I’m going to show you just this and a couple
other ones, but basically this means don’t print the command you’re about to
execute, just execute it. So let me show you an example real fast of what I’m
what I’m talking about.
Targets to show silence and non-silent commands,
or a suppressed command, whatever you wanna call it.
Basically not printing the command,
just executing the command.
So I’m gonna name a target silent commands,
and it’s not gonna depend on anything.
So that’s why I’m gonna hit enter here,
just like with menu and unlike the default.
So then I wanna do tab to enter a command
to enter a command and the echo command is going to say hello this is a silent command or a
suppressed command then i’m going to do basically the same thing again but i’m not going to put the
at symbol in front i’m going to say hello this is a non-silent command so what we should see is that
if we exit the silent commands target we should see first this message gets printed on line 75
and then second we see the full command including the echo statement that shows up on line 76 and
on line 76 and then third we should just see the message hello this is a non-silent command
let me oh and then if it’s a phony target meaning it’s not an actual file on the file system
that you expect to be built we have to do phony and phony is actually a target notice how i’m
saying dot phony colon indicating that that it’s a target and we’ll just say that it depends on
and we’ll just name the command that is also a phony or the target that’s also a phony
you can specify all of the phony targets in one line if you really want to but for me that kind
of feels messy and you’re allowed to like add add add dependencies so notice on line 70 i’m adding
menu as a phony target and then on line 77 i’m adding silent commands as a phony target
the way you do it is up to you but i like to do it this way so i’m going to do silent commands
and then I’m going to do make silent commands.
So now we’re actually going to execute the silent commands target
so we wouldn’t see a menu.
And then we’ll see an example of the silent commands.
Notice how the first one that says,
hello, this is a silent command.
The actual echo command is nowhere to be found
because that whole line was muted
and only the output of executing the command gets shown to you.
But for the second one,
remember we did not put the at symbol on the second command.
So the whole entire command gets printed.
the whole entire command gets printed echo and then quote this is a non-silent command and then
that command gets executed and here’s the result it’s the same string except just without the echo
so you know this line right here is this is the command that we’re about to execute whoops
this is the command we’re about to execute and then the last line is this is the result of
executing that command why would you want to print your commands kind of useful sometimes to see the
compiler statements that show up there have been a few times or more than a few times in my life
times in my life where I was running a make file and just kind of watching it and it was printing
out all the commands that it was executing and then I realized oh my god that’s wrong because
it was printing and I was able to fix that so you know you don’t have to print your commands but
you probably want to so let’s see what else can we do okay so here’s another thing that I want to
show you how to ignore failures so sometimes if you’re compiling your source code won’t compile
So then that means the compilation command failed.
Or if you want to do any command in general,
and for some reason it returns false or it fails,
then that’s considered a failed command.
The make build system will stop execution of any target
as soon as it encounters a failure.
It’s kind of a good thing.
What if you had a huge complicated target,
which you can do it this way.
I’ve done it this way before.
What if you had a huge complicated target,
which not only builds everything,
but then also tries to deploy it to your remote server?
deploy it to your remote server.
Well, if your program fails to build, you probably don’t want the deployment part to
execute.
So you would hope that it would stop as soon as something failed to compile.
Right.
So we should try to know the difference between failed commands stopping the make build system
and failed commands being ignored by the build system and just continuing.
So we can do it both ways.
So I’m just going to do a little comment here.
or ignoring failures and then i’m going to do ignore failures so that’s a target no dependencies
and i’m just going to do false false twice in bash if you just you know do a false
then it will be interpreted as a failure um so you know if i put true here it will be interpreted
as true and this is not necessarily a make file thing this is a bash thing just like the command
just like the command line. Let me show you real fast just just to prove it. I’m gonna prove it to
you. Okay, so hopefully you remember if you can do echo dollar sign question, it’ll tell you the
return code of the last command that you executed. So if I type echo hello, and then as another
command, I echo the return code of the previous command. Now it’s going to basically tell me
echo and then it’s going to tell me whether or not echo succeeded. You see a zero indicating
succeeded it would be really weird if it didn’t succeed but you know whatever now i can modify
this command just to type in some random stuff like what if i type in this notice how the second
part the echo return code part echoes a 127 anything but a zero is considered a failure
usually so it’s telling me 127 and that’s sometimes a standard code meaning something
wasn’t found i could also just do true and that should give me a zero because you know in bash it
bash it understands boolean so it’s like true success that’s a return code of zero it’s weird
but that’s what it is if i return if i execute false then the return code should at least not
be zero it’s probably going to be a one yeah there we go so just letting you know if we just
execute the statement false by itself or true by itself then that’s telling us
um that’s sort of like simulating a failed compile command or something i guess we could put
commands in there if we wanted to but i don’t really want to run the compiler just for this
in the compiler just for this so then I’m gonna do echo this echo statement
should be reached as previous errors which I’m using false to represent should
be ignored so now why how do I know that we’re gonna ignore the errors because I
put the little minus in front of the false notice how before it was either an
at symbol or nothing at the beginning of a command remember the at symbol does
Remember the at symbol does suppression.
The minus symbol says try to run this command,
but if it fails, ignore it,
continue with the recipe for that build target.
You know, recipe meaning all of the commands
that you want it to execute.
So that means the ignore failures target
should actually fail twice and then make it to line 84
where it just prints that message.
Then we’ll do, well, we’ll mark this as a phony target.
practice to try to mark your phony targets i’ve i’ve not done that in the past and eventually
regretted it a few times so then i’m going to do stop on failures is another target and we’re going
to do false whoops no minus because we actually want to stop on failure false and then we’ll do
another echo and we’ll say this echo statement should never be reached as previous errors
false should cause build build termination okay I’m kind of verbose but I hope this explains it
we’ll name this as a phony target too phony you’re a phony failures okay so we can run both these
maybe I’ll put a comment here just to show you it’s two separate things I kind of like doing that
we can run ignore failures and then we can run stop on failures separately to see what happens
Let me get rid of this terminal window.
Okay, so I’m going to do clear and, what was it?
Ignore failures and then stop on failures.
So ignore failures, notice how it prints false
because I forgot to put an at symbol in front of it.
And then the make system itself says,
hey, error, that command failed, error one.
Remember before we had the return code one
when we just typed in false.
me that it received return code one from trying to execute false inside of bash so it detected
that a failure occurred so these two lines go together then the same thing again because i have
two false statements in there and then the last statement says this echo statement should be
reached as previous errors should be ignored because we use the minus symbol i’m having a
hard time selecting text here i gotta i gotta go home and practice my text selection actually wait
So notice how, you know, the minus symbol right there says, let’s ignore.
So now let’s do stop on failures.
Stop on failures.
Notice how the first false showed up as an error to the make build system.
And then make just said, we’re done.
We’re not ignoring this error.
We’re just done.
So keep that in mind.
Anytime you have commands that need to stop the build, if they fail,
build if they fail you should not put a minus in front but anytime you have commands where
everything just needs to be executed even if some stuff fails you want to put a minus in front of
there we’ll talk about a clean target a little bit later which usually you know just continues
even if there’s a failure so we got silent commands ignored commands or ignored failures
and failures that we’re actually going to stop so now we’re going to get into the the meat and
make file it doesn’t really do anything it doesn’t build anything for us I’m just kind of showing you
you know how to work with the make file like the very basics now let’s actually start to build
things so the first thing I’d like to do is I’d like to make a target for the build directory
if you you know again if you just kind of like look at my file system here I don’t actually have
a build directory right so I want to make sure that a build directory exists I could of course
dir and then type build but I want the build system to do this automatically for me and also
as a lesson for what we’re doing so you know be sure the build directory exists so I’m going to
name a target and this is not going to be a phony target I think this is our first non-phony target
right yeah oh I forgot to put la default as a phony might regret that one day the defaults
you is phony and everything’s funny. Okay. So now we have our first real build target. I’m just
going to name the thing that I want to build. And I’m going to name it by typing the build directory
variable. I could type, you know, underscore build or whatever it is that I put for that variable.
What did I put there again? Underscore build. Yeah, I could put that if I wanted to. But again,
try not to hard code things. It’s better to stick things in variables if you can.
So since this is a target, I’m going to put a colon. It doesn’t really depend on anything,
because if we’re just talking about building that folder all we really have to do is execute a command
to just make sure that the folder is created right so i’m going to do no dependencies and
then i’m going to like maybe do a little all say function call just to announce that we’re going
to ensure the build directory a lot of people don’t do this but i’m going to do it ensuring
the builder and now i could put you know what the builder is probably a pretty good idea so that i
probably a pretty good idea so that I can see what I’m trying to ensure let’s
just run that real fast if I say oh shoot I guess I would have to do make
and then name build let’s see if that actually works yeah okay so I’m naming
the build directory you wouldn’t normally actually name the build
directory in the command line but I’m doing it so it says we’re ensuring the
build directory and then now it says the build thing is up to date because the
because the target finished executing,
but nothing actually got created, right?
So what I would like to do before we actually create it
is replace this builder variable with a special variable
that GNU make gives you called $at.
The $at variable, and I’m quoting it,
you don’t have to quote it,
it always ends up being the name of the current build target.
So again, it’s a bad idea to hard code
It’s a bad idea to hard code lots of different values
and it’s way better to put things into a variable.
But at the same time, if you can get away with
hard coding a variable name multiple times
and you can use something more generic,
well then that can also increase the power
of what you’re doing, make things easier for you
and easier for you to debug.
Save yourself some time, right?
Because coding is already hard enough.
So if I do dollar sign at,
it should show up as whatever the builder contains
because that’s the current target.
So let’s run that one more time.
that one more time and we’ll say clear and make build notice how it says ensuring the builder and
then it quoted the current target which is underscore built nice so we got that now let’s
actually run a command to create the directory that’s just going to be you know in linux the
command mkdir to make a directory and again i could put a builder here but it’s probably a
special variable that will change because what if later on i want to change the name of that variable
uh if i did then i’d have to change it in three places now but uh you know and possibly way more
places later down the line but if i use that special you know dollar at symbol then i only
have to change it in one spot okay so mkdir so that basically says it’s going to look to see if
that folder is created if it’s not then it’s going to create it with that command so if i do that
So if I do that, notice how it executed that command and also printed the command.
So it’s like mkdir build.
If we list the directory again, notice how there is a build directory.
Okay.
You go inside of there.
Everything’s fine.
I’m going to remove it real fast.
Whoops.
Okay.
All right.
our make file until it’s somewhat decent it’s not going to be amazing but it’s going to be somewhat
decent okay so we have a target that will build the build directory now let’s make our first real
target that is it’s going to be a phony target but it’s going to it’s going to do something for us
so run the program we’re going to make a target called run and so if you type make run then it
which in my opinion would be building the program first.
Your opinion may vary, but you know, so I’m going to do it this way.
I’m going to say run colon because that’s the name of the target.
And I’m going to say that the run target depends on the build target.
So remember this means that before run can actually execute its steps,
it will go quickly to the build target and execute all the steps in its recipe.
So essentially I’m saying before we try to run, let’s build.
So it depends on build.
By the way, build is not created yet.
We’re going to do that in a second.
not created yet we’re going to do that in a second the only line in this recipe that i’m going to put
is just the name of the binary meaning after build is finished running i can assume that
the entire executable is actually built and it’s ready to run so then i’m just going to run it so
it’s just like first go build it somewhere else and then run it and again indirection and modular
thinking is is a lot more powerful than just sticking all the build commands right inside of
run target and duplicate all those commands for the build target so this is way better and then
i’ll name run as a phony because this is not an actual file i’m trying to build the run the there’s
no file named run then we’ll do the build target build and link the object files and executable
okay and this is where it gets a little funny so we’re going to make a target called build
actually build everything we shouldn’t hard code them here we should treat every source code file
as as one separate thing that needs to be built every corresponding object file that comes from
a source file should be one separate thing that that gets built from the source code that needs
to be used to build i guess is what i should say so what should the build target depend on like
before we can say that build is satisfied.
How about just the executable file itself?
So I’m going to say the bin.
So our main program,
it should be totally built and finished
and set up and created and everything
before we can say build is satisfied.
And then hilariously, once that’s satisfied,
then the build target is totally satisfied too
because it doesn’t really do anything.
It just asks, you know, this is more of a shortcut.
Okay, so now let’s,
Let’s maybe first before we link let’s start writing commands to actually build the source code files. So I’m gonna do
build the first object file
So remember we have three source code files here. We’ve got first dot CPP and
Second dot C and third dot ASM. So that’s just three source code files. They should map to three object files
Let me let me draw this for you for a second before we talk about it further because I want to make sure
that what I’m about to do is crystal clear when we’re talking about like the dependency graph.
Okay. So my mouse is all over the place. I gotta execute a command real fast.
I have many monitors going on right now and the pen just keeps forgetting that it’s only
supposed to be on this one monitor. Okay. So I got that. Okay. So the first thing that we
executable called main.
So I’m just going to draw that on the graph here for you Windows people.
You can imagine there’s a dot exe on the end of it, but in, you know, in Linux and
other link and other operating systems, we don’t really need that.
So main, we want to build that.
That’s the, that’s the primary thing we’re trying to build.
What does it depend on though?
What needs to be built before we can, we can create our executable?
Well, we need to compile every piece of source code into a corresponding
all the object files will be linked together into the executable so maybe i’ll i’ll write
down first what the uh what the source code files are going to be so i’m going to do let’s see first
sorry about my writing i don’t have a smoother right now first dot cpp and i got like a really
small draw pad um i’m i’m making lots of excuses but i think probably my penmanship in real life is
maybe even a little worse i don’t know i was at a job a long time ago and i was taking down
names of customers it was like a tech support center and my boss after he saw the paper that
i was writing down he just looked at me and he went chicken scratches and then he walked away
okay so we have first dot cpp and then we have second this is why i like typing second dot c
okay so we got that and then we’ll have third dot asm so third maybe i should go slower a
At least I’m not making misspellings.
I know these are simple file names, but it could happen.
So we have three source code files.
They don’t really depend on anything because they just are themselves.
We don’t need to build these source code files.
So we’re not going to draw any arrows indicating that these files depend on something else.
They’re kind of like the primary source.
Like these are the things that would actually go into your Git repository,
whereas the object files that we’re going to generate and the executable that we generate,
they would definitely not go into your repository.
your git repository. Okay, so what’s going to happen? First.cpp is going to generate first.o.
We’re going to type that out in a second, but basically first.o. That’s awful. That just looks
disgusting. I’m going to keep it though. Okay, so in order to indicate dependency,
we’re going to draw an arrow from first.o to first.cpp indicating, all right,
if we’re trying to build first.o, then it will look at first.cpp and it’ll check.
check does it exist if it doesn’t then there’s an error if it does exist then then the the the
build system is actually going to check the modification dates of first.o and first.cpp
assuming that a first.o already exists it’s just going to check it’s going to say
does is first.cpp newer than first.o if yes then that means i have to recompile first.o if no i’m
not going to recompile it and i’m going to save a bunch of time this is kind of the heart of why
So same thing for 2nd.0.
2nd.0, I’ll just circle it here, and we’ll say that it depends on 2nd.c.
And then, of course, the 3rd.0 object file, it’s going to depend on 3rd.asm.
So again, notice how they don’t really depend on each other.
Maybe if you have a C++ file, maybe if you have a C++ program that’s really complicated,
maybe your first.o, it would depend on the CPP file,
and it might also depend on some other header files that you have set up somewhere.
So then there would be more circles and more arrows that we draw.
But for now, it’s just a simple program, so I’m just going to leave it this way.
But just keep in mind that when we name dependencies in the make file,
you want to name a dependency of everything that a source file depends on.
So like first.o, it’s definitely going to depend on first.cpp,
includes that are custom or from someone’s library and not the C++ standard libraries,
then you would want to say that first.o depends on those as well. Because if you change
a header that you made elsewhere in the program, then you probably want to recompile
that corresponding object file, right? So just keep that in mind.
Anyway, we’ll do first.o. Okay, so now the executable program main, that needs to be
all the object files so I can just say that it depends on all three object files
with just this right here and this is the basic idea of what we’re about to
type up in the make file itself keep in mind that the make system will actually
build this graph in memory under the hood to try and figure out which things
actually need to be built or not you could even launch make with multiple
threads I’ll probably forget to do it but it’s a things like dash J and then
things like dash j and then the number of threads you want to launch in order to totally saturate
your cpu and have as many things compiling as possibly can be compiled make will figure out
which things don’t actually depend on each other and then build them in separate threads at the
same time so your build system is just like so much faster so let’s see we got uh corresponding
object dial for every source file and then we got the binary that depends on the object notice how
source code file what would happen is when we wanted to build the let’s say we wanted to just
you know this is the the target the binary target that is just named main what will happen is it’ll
look at its dependencies and the way we’re going to write it it’ll say okay it depends on the three
object files if they’re already there and they’re already satisfied then it it won’t actually
if they’re already there and they are all newer than their corresponding source files then none
corresponding source files, then none of them will get rebuilt. If main itself sees that it is newer,
like the existing on disk version of main is newer than all the object files, then that tells the
build system, oh, I don’t actually even need to relink the binary at all because the object files
didn’t change, which means the source code didn’t change. And so you could actually like type make
build and nothing happens because nothing needs to happen. It’s going to be pretty cool. I’ll show
I’ll show you in a moment.
Okay.
So for now, I’m going to try not to erase this.
I’m going to do a toggle.
Do I can, I don’t know if I remember, I think it’s like control.
Let me do the menu here.
I don’t want to screw it up.
Okay.
It would have been control.
Okay.
So I’m going to toggle visibility.
Just get that off there.
Just in case I want to show it to you again later.
So now let’s make the first object file.
So remember the first object file is first.o.
to create a target that will build the object file based on the first source code file.
I’m going to put a colon there to indicate that it’s a target.
And then remember on the right side, we can do dependencies.
Well, we have to say that this depends on the first CPP file.
If we didn’t, then every single time we run the make system,
then this object file is just going to get built over and over and over again,
even if it doesn’t need to.
So now with a dependency there, it’s going to look at first.cpp
and then it’s going to look at first.o if one exists if one doesn’t exist it’ll
definitely build it but if one does exist it’ll go is first.o newer than
first.cpp if it is don’t do anything but if it’s not then rebuild it make sure
it’s fresh here’s the other thing though I kind of wanted to build everything
inside of the of the build folder so I’m gonna add the variable build in front of
it let me do it first without doing that so that you can kind of see what will
So it’s going to depend on first dot CPP.
And then there’s another thing you can do in make files where you can,
I forgot the proper name for this,
but you can specify other types of dependencies where they only need to exist
as a dependency.
They don’t necessarily need to be freshly fully updated because,
you know, the way files and folders work,
if you start adding or removing files from a directory,
then the directory thinks that it’s been updated.
And so that can kind of throw off the build system’s thinking
the build system’s thinking because your build directory will always think that it’s been updated
if anything happens. But we don’t really care about the date of when the build directory was
built. We just want to make sure that it exists, right? So we put a little or bar and we put those
types of dependencies on the right side of the or bar. Okay, maybe I’ll leave a comment or something
at a later date specifying the official name for that, but I mean, that’s how it works.
So that’s the recipe.
It’s got dependencies.
You know, in the past, I’ve said when you name a dependency,
it goes off and make sure that that dependency is resolved.
Well, that’s true for phony targets and targets that sort of do other things.
But in this case, if we’re just naming a file, all it’s going to do is just look
at the modification date of the file.
So now let’s do the recipe.
I’m going to do CPP, which means that’s the C++ compiler.
And I could write, you know, G++ and all the commands here directly
directly if I want to, but it’s kind of a waste of time.
And it’s not, it’s not very smart because we said before, what if you want to change
the flags and you have a hundred objects that you’re building, you probably just want to
be able to change one variable instead of a hundred different things.
So we’ll do CPP flags and then we’ll, let’s see, we’ll take first.cpp as a source.
You can imagine again, this is like, you know, G++ happening inside of here and then wall
inside of here.
and then at some point you specify the source.
Okay, so just keep that in mind.
And then after I specify the source,
I want to specify the output
and the output is going to be first.o.
And now let’s use another trick
which allows us to make slightly more powerful make files.
So again, you want to avoid hard coding stuff if you can.
There’s a special variable.
Well, okay, let’s go back first to the other special variable.
Remember $and, or sorry, $at.
that always refers to the current target right so first.o is the is the current
target so I don’t even have to type first.o here I can just do dollar sign
at there’s another special variable which just basically means the first
dependency that’s been named so if I just do quotes and then I say this it’s
weird I know dollar sign and then angle bracket pointing to the left or like a
typing first dot CPP. If you wanted to send a bunch more stuff, like if you wanted to
send all of the names of all dependencies that you named into the command line, then
you can use the carrot like dollar carrot. That’ll do all of the dependencies. But if
we’re just compiling one source code file, you probably want to just do one dependency.
Like for example, if I had other thing dot HPP as a dependency, I probably wouldn’t want
because then that would be telling the compiler,
here, I want you to compile first.cpp and also other thing.hpp,
which probably wouldn’t work.
It’s probably not what you want.
So instead, if I do this like arrow to the left,
then it will only give it first.cpp rather than both dependencies.
But I guess we’re simple.
We don’t really need all that right now.
So we got that finished.
Let’s see.
We now have the binary.
okay this is only going to run if i do make first dot oh maybe i should do that right now so that
we can we can take more baby steps to making this more complicated so i’ll do here and make
and then i’ll do make first dot o so notice how when i typed first dot o it went right to this
target no problem and then it started to you know it looked at first dot cpp just to see you know
built or not and it it also looked at the build directory as a dependency and
then finally it actually compiled so what we should see right now is first
dot o sitting in the directory and then also we should see the build directory
again let’s just double check that so if I list everything notice how the build
directory exists if I go remove and then remove the build directory and then
clear it again notice how we also have first dot o so I can say remove first
So if we go clear and list everything again, now I’m going to do one more time.
Make first.o and then if I do list, then the first.o got built and the build directory got built.
Notice also that as soon as I ran that command, it did make dir on build.
So it built the build directory.
So that was the build dir target.
And then it went over to the first.o target and it executed that command to actually compile it.
All my variables expanded, so it’s like G++, wall, pedantic, whatever.
That special variable with the arrow pointing to the left was the first source code, or source code file.
And then the $at variable expanded to the current target, which is just first.o.
If we run this again, I’m not going to clear it, I’m just going to go make first.o.
Notice how nothing actually happens.
It’s missing two lines.
Up here we had two lines where we made the build directory
and we also compiled the object file.
But notice how here it just says the object file is up to date.
It did not recompile it because it can tell that the source code was not changed.
Let me go into another directory right here and I’ll just modify the source code file.
I’ll just add an extra line to it and then I’ll go back here.
and if I compile it now we should see that it chooses to compile it again
because now the source code is newer than the object file so if I go make
first notice how it did not build the build directory because it already
existed but it did rebuild the object file because the source code was updated
if I immediately try to build it again it does not need to rebuild it because
the source code didn’t get updated since the last time I built and so it says
it’s up to date up to date sometimes you might see a message that says something
just says something like nothing to do, I think.
So just keep that in mind.
We’re getting a little bit better now.
But I’m also making a mistake here
because we have a build directory
but there’s nothing inside of it.
I wanna put everything inside of my build directory
just to show you how annoying this could be.
Notice how we got a first.cpp and a first.o.
Imagine if you had a million source code files,
you’ll have a million object files
sitting in your repository or your directory
kinda cluttering the place up, right?
cluttering the place up, right? I don’t like it. I’m not a fan. You probably want to edit your
git ignore so that you don’t accidentally put object files into your repo. But for me, I think
it’s better to just ignore, git ignore a build directory, and then just stick everything inside
of the build directory. It’s up to you, but this is the way I’m going to do it. So how do we get
it to be inside of the build directory? First, let me remove the object file. Okay, let’s see,
I think I can get rid of that. That’s responsible for the swap file. We probably want to put
star.swap inside of gitignore so that the temporary files from nano don’t go into our
repository. So now I’m going to prefix this object file. And a lot of people don’t do this,
but I think it’s pretty cool. I’m going to do build dir slash the object. And then now
everything’s going to show up inside of the builder or at least that one. So if I remove
directory just kind of list things again then I can do make first oh and then
notice how it mmm what happened oh what did I do wrong well I did something wrong
okay hang on let me see if I can fix that first oh what in the heck did I do
second and third. Okay, so I didn’t break anything.
First.o. Oh, because when I typed this on the command line, I was typing first.o and
well, that’s not the target anymore. The real target now is underscore build first.o.
So maybe this is a good time to stick our binary target inside of there.
So we have the binary target. I kind of ignored it for a second because I wanted to show you an object file first.
We want to make the main file that gets executed.
We have to link everything together.
So, well, maybe this is a better description down here for that.
And then up here, I’ll say alias for just building the binary.
Whoops.
Binary building.
Building.
There we go.
Okay.
So now we’re actually going to do it.
So we’ll put bin.
So now bin is its own target, meaning our main program that we want to create
main program that we want to create is uh it’s going to happen somehow the uh dependencies that
it needs though should be all of our object files that we intend to create so first you know one of
its dependencies should be uh inside of the builder and i know this is annoying to type these variables
all over the place but kind of the price you pay for having everything outside of the working
directory but i guess that’s why a lot of people don’t do this we say builder first dot o so
So basically saying before we can create the executable, we have to first build the first
object file.
And then we’ll just name more dependencies.
I’ll say the builder, we separate dependencies using spaces.
That’s another reason that make has problems with spaces if you’re going to do like an
absolute path or something.
So then we’re going to grab the second object file as a dependency and then the third object
file as a dependency.
means we just need to set up targets for each one of those object files we only have first.o
set up so far we’ll have to set up the second and the third one so aside from those dependencies we
want to make sure that the builder is actually created so we’ll put builder as another uh you
know non-time stamped uh dependency like we did with the object file then the command we’ll actually
and remember CPP is just GCC and then the link flags is the stuff that we set up up above
which is basically going to be you know wall pedantic G and then M64 no pi no you know all
the stuff we don’t want to type manually at the very bottom so we’re going to do that link flags
you know we want to give it every single object file as an input so we’ll name you know first.o
and then we’ll name second.o second.o and you know i’m getting irritated i hope you can tell
already i don’t want to type all these things so we can use a shell trick we can put a wild card
there we can say how about i just give you every file asterisk that ends in.o so star.o basically
on that command line it’s going to go look into the build directory and just
give it every object file that it sees it’s not a great idea if you have
multiple different executables that you want to make and they’ve all got their
own main functions and they’re all in the same builder but in our case we only
have one executable with one main function so we can just throw all of the
object files we want at it if you had other types of object files you wanted
to link into your executable like for example shared object files you could
object files you could basically do the same thing but just go you know builder star star.so
or maybe not even in the builder if i if let’s say if i gave you a shared object file
and i wanted you to link that into your program you would just specify wherever it was
you know maybe it’s in the root something.so maybe it’s in a different folder um and maybe
you want to create a variable up top to just kind of like name where the shared object files are
libraries are it’s up to you but I’m just letting you know this is how you
would do it to get a shared object file in there as well for this program it’s
very simple we just are gonna grab the object files and that’s it so now we’re
just ready to specify the output file with a dash o and we’ll use that same
trick again dollar sign and at so we don’t have to specify the the full
target which is just going to be the bin variable so let’s see what do I got to
If we run it right now, it should fail because it’ll complain that we don’t have second and third built.
But at least I have enough now to use the make run target.
So we can stop specifying the names of files and just say something like make run or make build and it’s just way easier to type.
So I’m going to type make run.
It’s going to depend on build.
Build is going to execute before run even tries to run.
Build is going to say, well, I depend on the binary, the executable.
Then that means the binary’s target is going to run.
run it’s going to depend on all these object files so before it even tries to do anything
it’s going to go down here to the first dot o object file it’s actually going to build it
then it’s going to come back and try to handle the next dependency which is second dot o
but there’s no target for second dot o and it’s not on disk um so it’s just going to fail at that
point so let’s see if let’s see if i’m right we’re going to do make run clear and whoops
Make run
Okay, so ensuring the build directory. Oh, I forgot to mention that yet
It’s gonna ensure the build directory then it actually builds the first object file so far so good
Then it complains no rule to make the target second auto so we can’t continue Oh
drat
By the way just to show you I think it’s like a J
Let’s say we wanted to launch this with eight threads. I think it might be low
Is it lowercase or uppercase J? I can never remember. Let me try lowercase
second how about uppercase nope okay so it’s lowercase well I guess it just fails right there
I was kind of hoping it would try to spawn off two threads to look at second and also third
at the same time and then fail twice maybe that’s why it didn’t do that because it automatically
saw right away oh we can’t do anything so let’s just cut the whole build process so now I’m going
Cut cut cut paste paste and then I’m going to say build the second object file
So we’re going to depend on seconds
There are actually advanced patterns you can use so that you don’t even have to specify the object files you can
There’s there’s these things that people do I did it a couple times
But I don’t really like it where you can have make sort of scan your source code directory for all CPP files and
Automatically compile them in the exact same way and then the output file is like
And then the output file is like the name of the source code file and then.o concatenated at the end of it.
But this video is not like that advanced, so I’m not going to do that.
So we’re going to compile second.c into second.o.
And we need to make sure the build directory is created.
And instead of using the CPP compiler, we’re going to use the regular C compiler.
And then we’re going to do cc flags right there.
and I think that’s all we need so then we’re gonna do the last object file by
the way in nano I’m going control K to cut and control you to paste and it’s
kind of convenient if you want to like duplicate you know a few lines we’re
gonna use the ASM to assemble the assembly code so I’m gonna do third dot
what it was and then we have to use a totally different command now we have to use the assembly
assembler variable so that’s the name of the tool that assembles and then we’ll give it the assembly
flags i mean i guess it’s not that different and then we’ll say give it the first dependency which
is just the source code and then output to the at target so we should have like a lot of stuff
ready to go let’s see if this works now if i say clear and make run then what is it going to do it
then what is it going to do it the first thing it does is it compiles the second object file
notice how it did not compile the first object file because remember we just compiled that
and we didn’t change the source code so it just ignored it and we have a faster compile
then it assembles aka compiles the assembly to its own object file and then
now that all those object files are satisfied notice how like you know for the binary
all of those object files first and make sure the build directory is there. Then finally it can
execute its own recipe, which is just one command saying, you know, let’s link this stuff together
by grabbing all the object files. So that’s this line right here, the linking flags and the linker.
So it’s like GCC and all the linking flags, the no pie and all that stuff. And it’s expanded that
variable. Remember we put builder slash star dot O. So that glob right there basically says grab
glob right there basically says grab all the object files from the build directory and then
output the main program in the build directory no no i think even if you’re using a build directory
sometimes people will still put their executable in the root i don’t know it’s up to you i don’t
really care finally after all that is done you’ll see let’s see what we actually did is we said let’s
its recipe it goes down here for build and then build says first we need to build that binary
and then binaries like first we got to build the object files and then you know all those
get built finally when the object files are built then the binary gets to link itself
when it’s done linking itself then we go back to this point because now finally the dependencies
on line 108 are satisfied then that means the build target its dependencies are satisfied
dependencies are satisfied. It would execute its recipe, but there’s no recipe there. So it just
goes back to run because in the first place, run said, I need you to make sure that build is handled
before I’ll execute my recipe or all of my statements. So finally, after all that stuff
was done, then it’s just a command line that just names the executable, which is going to run it.
And that’s this last part right here. So right there, it just names the executable using a
using a whoops using a relative path to our main executable so now below that
line the program is actually running it’s like hello from first hello from
second hello from third and watch this if I do make run again nothing happens
except executing the program notice how none of the object files are getting
built the binary is not getting linked nothing is happening because it’s
already done all it does for the run target now is just run the executable
run the executable. It’s the same thing as if we did build main, just to run it. That’s it.
You know, of course, it’s a little more proper to go dot slash, right? You don’t have to use the
dot if the thing you’re naming is in a different directory, but you do if it’s in the same directory.
Like if we just went into the build directory, by the way, here’s all our build files. They’re
in one convenient place. You can do a git ignore on that, or you can just delete the folder if
if you want to clean everything.
But we can’t just type main here, it’s not going to work.
We have to do a dot slash main because we’re in the same folder.
But if we’re one level higher, then we can just name build main and it’ll work.
Okay, so let’s do another thing real fast here.
Let’s do another target that is kind of typical.
It is the clean target.
useful when people have object files just scattered around the root of their of their file system
there their repository but i like to add it in anyway for mine even though mine’s a little bit
cleaner right now so we’ll do another target called clean and we’ll just say clean the uh the
build area system thing stuff and i’m just going to put the word clean with a colon no dependencies
meaning it’ll always run right away and not depend on anything it’s obviously a phony target because
It’s obviously a phony target because I’m not going to make a file called clean so I’m
going to stick that as a dependency to phony.
And then what is it going to do?
Usually this is where you put something like let’s remove star dot o, let’s remove all
of the object files and then let’s remove the binary file and let’s remove anything
that we built so that we can just clean it.
Maybe I could put that here, build dir slash o, I’m going to make it a little bit better
in a second.
inside of the builder and then we’ll also remove the binary.
So when we go back into our file system,
if we list the build directory,
we should see that it’s empty after we run this.
So I’m going to do that right now.
I’m going to do make clean.
And then you can see that these two removal commands got executed.
And then if I list the directory again, notice how it’s empty.
But the remove command will fail
if it tries to remove something that doesn’t exist.
to remove something that doesn’t exist so we’ll actually get like a a nasty message from clean
or from make if we try to clean again so we say make clean it says remove tried to remove object
files but there was nothing inside of there no such file or directory so then it just stops the
whole process well that seems okay for now but what if you uh what if you had gone in and removed or
and that stopped other things from being cleaned you’d probably end up being
pretty frustrated right so for the clean target I’m just gonna say remove is a
command that won’t stop the build or the target so I’m gonna put a minus in
front of there meaning don’t stop if something goes wrong just keep
executing all these commands and so now if we do clear and make clean notice how
it actually tries to remove both types of things it doesn’t just stop on the
I guess this is the first problem.
It’s trying to remove the object files.
Then remove says, no, I can’t do that.
There’s nothing there.
And then make says, I’m ignoring you.
Stop whining.
There is no bathroom.
And then the same thing happens for removing the executable main.
But we can make this actually a little bit more efficient.
I mean, this is kind of what you’ll do if everything is sitting at the root of your repo.
But if we simply have a build directory and you’re absolutely sure that you already have
you know set to the correct value then you could just remove i don’t know if i want to do it now
because i’m getting scared you could just remove the entire build directory i’m going to do it but
i’ll explain to you why i kind of don’t want to do it in a second we could remove the entire build
directory so like with the remove command remove recursively the entire build directory
just so you don’t see it if you feel like it’s an eyesore so i’m going to do make clean
do it again do it again do it again and notice well why wasn’t it
i feel like it should have failed but i guess remove felt like it wasn’t a failure if build
didn’t exist but notice how the builder is gone so i didn’t really have to specify
the object files but it’s a little bit safer i don’t know it depends on your philosophy
this is way more compact and nicer that was my first feeling but it’s also a little more
there into the variables area and i like change the builder somehow i change its name or i change
its value and i accidentally now have this variable pointed to like a dangerous path
and then if i type make clean it just erases everything not sure i really like that idea so
i’m going to do i’m going to go back to the original way which takes more typing
remove every object file and maybe before that let’s remove the binary so if we do
if we do make build which by the way will only build things it won’t run anything remember
the dependency graph that we looked at a little while ago or not the dependency graph that we
explained the dependencies so it does everything required to build but it doesn’t actually run the
program and that’s fine i just wanted to go look inside of the build directory
and you can see it’s got all the stuff there so then if i just run make clean one more time
by the way, if I try to run make clean from here,
there’s no make file in this directory.
So nothing’s going to happen.
So I’m going to go up one level, make clean.
And then if I list the build directory,
there’s just nothing inside of it.
Okay.
All right.
Let me just look at my notes right here.
I just finished building this whole giant make file.
It’s not even like that complicated,
but I hope that you’ve learned a lot
about making make files at this point.
this point i hope you kind of like the idea let me just double check there’s nothing else i wanted
to say for this video oh i should point out that gnu make is not the only build system available
it’s just the one that i like the most um remember you can also put shortcuts in there like any
command line that you could actually type could be part of the recipe of a target it doesn’t have
to only build it doesn’t have to only be build targets so again you could make commands in there
like deploy your executable somewhere or check something or like a long time ago I used to have
a recipe actually it was that program I was talking about at the beginning where I converted
an hour-long compilation to just like a minute I had another target in there called deploy so I’d
say make deploy and what it would do is it would build and if it built then it would run unit tests
on my code and if those succeeded then it would copy my source code to one of my other servers
my other servers then remotely it would tell that other server to build and run tests and if all
that succeeded you know if it didn’t succeed then it would just quit if all that succeeded then it
would shut down the the server that I had written as a running service and then it would copy the
freshly built executable to the right location and then it would restart the service so basically I
just like do a bunch of coding and then I you know run my test if everything seems cool then I go make
five minutes or whatever it took to run all the tests and do another compile and deploy everything
and my server was just totally updated just by that so there’s a lot of stuff you can do but
there are other great build systems out there there’s one called gradle there’s one called
maven there’s other like node.js systems there’s a ton of build systems out there
probably a slightly more popular popular c++ build system is called cmake it’s more tailored to c++
um but you know you obviously don’t need to do it it’s just i think like a little bit easier
depending on what you want to do so keep that in mind this is not the only system there’s a lot
out there to explore but this is this is definitely a great first step um let’s see what else can i do
oh we showed you the diagram showed you the info function and the targets and the dependencies
and the run build well i guess i’m out of stuff to show you that’s that’s all i had written down
that I wanted to show you maybe I could this is not a git video I’ll make you
know I have other videos forget but you know if this is a git repository you
would nano git ignore just because I kept mentioning this we’ll do nano git
ignore and then I’ll just say ignore the build directory and ignore swap files so
I’ll say like ignore the build directory and ignore anything that’s edited and
your git status and git add and git commit and stuff then the build directory wouldn’t even show
up so yeah kind of i guess it’s kind of unnecessary for this project okay i think that concludes this
video assuming i was actually recording this entire time thank you so much for watching i
hope you learned a little bit of stuff and had a little bit of fun i’ll see you in the next video
practice practice practice and start making your own make file videos and let me know
files in your own programs and let me know if you liked the content of this video and like if it
helped you i love receiving emails way after the fact when people say hey i love make files now
and it’s all because of you and i do it all the time because then i get to sit alone in my dark
room and giggle to myself it’s all because of me um okay i think that’s probably a good note to
hey everybody thanks for watching this video again from the bottom of my heart I really
appreciate it I do hope you did learn something and have some fun if you could do me a please a
small little favor could you please subscribe and follow this channel or these videos or whatever it
is you do on the current social media website that you’re looking at right now it would really mean
the world to me and it’ll help make more videos and grow this community so we’ll be able to do
videos, longer videos, better videos, or just I’ll be able to keep making videos in general. So please
do me a kindness and subscribe. You know, sometimes I’m sleeping in the middle of the night
and I just wake up because I know somebody subscribed or followed. It just wakes me up
and I get filled with joy. That’s exactly what happens every single time. So you could do it as
a nice favor to me or you could you could troll me if you want to just wake me up in the middle
of the night, just subscribe and then I’ll just wake up. I promise that’s what will happen.
Also, if you look at the middle of the screen right now, you should see a QR code which you can scan in order to go to the website
which I think is also named somewhere at the bottom of this video and
It’ll take you to my main website where you can just kind of like see all the videos
I published and the services and tutorials and things that I offer and all that good stuff and
If you have a suggestion for
Clarifications or errata or just future videos that you want to see
please leave a comment or if you just want to say hey what’s up what’s going on you know just send
me a comment whatever i also wake up for those in the middle of the night i get i wake up in a cold
sweat and i’m like it would really it really mean the world to me i would really appreciate it so
again thank you so much for watching this video and um enjoy the cool music
as as i fade into the darkness which is coming for us all
Thank you.