Learn to program with c - Part 17 - Makefiles (and a little on multi-file projects)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi there and happy Christmas now at this point in your programming career and I'm gonna assume that you're gonna have a programming career you it makes sense for us to start breaking up our program into multiple files as soon as you start writing bigger bigger programs you're naturally gonna want to compartmentalize or modularize certain aspects of it so say you've got a whole bunch of files that perform numerical operations you might want to put them in a separate file called numerical operations dot C now in order to do that or as a correlate of that we're going to need a way to compile our program with multiple inputs now if there's multiple files that our main output program depends on and in order to do that we need some kind of build script or build process you can manually do that but nobody does it would be a complete hassle for a very complicated program so what we use is something called make well that's warm like to do and it's a very common thing that you see everywhere so it's definitely worth covering now we're gonna look at good new make there's several implementations we're gonna look at the syntax of gonna make syntaxes of the other implementations very similar but can you make is probably the most common and what make analogy to do is when you have a program with lots of complicated dependencies so files so your main program has lots of dependent files each of those in turn depends on other files it allows you to recompile your complete program and only we compile those dependencies which have changed in order to do that make allows us to specify a target a bunch of prerequisites that's pre are and then have a tab and we have a rule and to build our target our target will be updated if it doesn't exist or if any of the prerequisites have changed and in order to build our target we follow the rule that probably seems a little bit obscure but don't worry we're gonna go right back to what we already know so so far we've been compiling a single file and have some sympathy for the people the early pioneers of programming because they often always wrote the entire program in a single file and sometimes I had to feed that entire program into a computer kind of byte by byte probably in machine code using either a punched tape or perhaps even manual switches whereas these days we've got a lot more flexibility in writing programs now so what we've seen so far is you've got a single single file something like hello dot see that perhaps just prints out hello world and what we've always done is we had a make file so how the file called make file so we've already been using make and inside that file we saw the following we had a target I'm using the terminology was just seen and our target was called default so mate when you invocate invoke make on a make file it always executes the first target it finds that's what it does and but it only does that if the prerequisite argot if this is a net name member of a file and if that following exists it won't execute the target unless the prerequisites have changed but all we had was a single target and the name hello so what exactly was going on here well actually the target default had a prerequisite called hello and we did not specify a rule for it so what this means is that because hello is a prerequisite it will then make one look for a target called hello and try and build build that because it knows it needs to build hello in order to build default which is what we're trying to build but there is no target in our make file for hello we could write one so let's write one just to see that we'll get the identical behavior in order to write target for hello we write the name of the target then we write the prerequisites and the prerequisites for our Hello executable our hello dot C that's just a single file that we use to compile it and then the rule for building the target hello this is a tab by the way and it's immediately on the next line so excuse me sir calling me that was my wife she's on her lunch break right so where where Hey so defaults our target and the prerequisite the default is hello so we create a new target for hello the prerequisite fellow is hello C and the rule for building hello is the following by the way c c/e C compiler is almost always alias to the default compiler on your system like juicy see or clang so you'll actually usually see disick outputted on the command line rather than clang or GCC now we're obviously taking our file hello dot C and we produce an output file called hello and that is actually it so when we call default may goes to target hello there are two ways in which your target can trigger a rule to be built first of all if this refers to if this is a file name so if the target is a file name and it doesn't exist and there are no prerequisites then the target will be built the time it will be built actually regardless of the prerequisites if the file doesn't exist because we need to build that target the other circumstance which can trigger the rule to be executed is is it if the file or any of the files in the prerequisite list or any of the targets because you'll see that we can actually essentially nest targets so if any of the prerequisites have changed then the target will be rebuilt changed changed since when you might be thinking will change since we last called make so whenever we call make make all keep track of all the prerequisites and all the files in those prerequisite lists and it will remember it will take a hash of the file and it will know when that file has changed it probably just looks at the the date in the file or something like that the modified date so if the file has changed then it knows to the target needs to be Rupert rebuilt and that's the kind of behavior that we want because whenever we change a file then we want the target to be rebuilt now so that's actually what's going on with our default case although we've put explicitly put the rule in there's an implicit rule that make knows how to compile a file when there exists the same name with the dot C extension so when when when your target is a file or is a a name and in that directory there exists a file of adopts a extension it knows to do that that's why we don't have to provide this um this target explicitly and that's called an implicit target there is another implicit target in make called which builds object files so say we had any I'm gonna look at this in a second if hello depended on an object for called front door which might contain a bunch of thumb which contains a bunch of functions that hello needs then our compilation step would also include Funko it's got an object fall just imagine this is on the same line and then it would still have our output specification now we have a separate object fall so we need to create target for it so we'll make we'll look for a target to build that prerequisite and so front door will depend on its prerequisite will be front door see there's a tab here and then what we do is CC amuse - C to create an object file func dot C and so that would be the rule to build an object file and this is another implicit rule whenever you create have a an extension dot o if in the same directory there exists a file with a dot C then compile that file but use the - C flag to create an object file which can then be used and combined in another compilation step to produce another program we're going to look at this just in a second but what we've also seen here then is that we have a target hello which depends has a prerequisite which is another target and that target is an object file and what you can see is that if say func top C changed that means that that this target would be out of date and would be rebuilt which would trigger this time it to be rebuilt as well because this period we'll be out a day so in that way changes cascade kind of upwards from the lowest dependence and trigger recompilation changes and that's very useful but also parts that haven't changed parts in the kind of tree of compilation do not get rid component all right let's take a look at that stuff so that we understand it more so we've got what we've got on the left is a file called hello dot C and in our make file we've got the the thing that we've done so far every time we have a single default called hello and when we type make you can see that he invokes CC with the file and then specifies an amphib file hello so let's create an explicit target so we can say that hello is a target it depends on hello dot C and then has little actually explicitly right now clicking follow that okay no there's nothing to be done because we haven't changed the file so let me just remove the object file and there you can see so this time we're compiling it using our target and you can tell that it's our target because the spacing is different like the spacing respect respects exactly what we have put in our spacing in make files you can put comments by the way which you proceed where they hash now let's have a look at those default targets that I was talking about so I said if you have a target that is the name of a file and if a file exists in that directory that is the same name as that target that ends in C then it will there will be an implicit target executed a placeit rule sorry executed for that target so what we've got is a name here and we're not which we've literally just got the target and there's no prerequisite so let's see what happened okay again let me just remove the old one and you can actually see them that that implicit rule has been executed so we don't have to specify it and let me show you the rule then for the object file so we'll create another file called test OC or now let's pull it in from the dulce and we'll just add in there a function it's going to print hello there now in our make file we could have a target good fun oh and we're not going to put any rule or any prerequisites now ignoring the warning because yeah we haven't included printf we wish we should include print standard IOH we should include son older age but we didn't so ignoring that just look at the rule that was actually executed so we used a - see there specified the object file and the input was fun dot C and of course we can do this manually ourselves so by the way the output file will bite if I remove funko just to show you to show you this and if I compile function with this - C tag your notice of thunder o has been generated so the default app of output name would be funk or the target file with the dot o appended but we can also explicitly say fun fact let's do something different and then you can see that there's a funky duo so that's two implicit targets one for object files and one for executable files and all of our C programs are going to be comprised of C files header files which are never compiled object files or extension and library files static libraries and dynamic libraries and they're the only things we will ever pass into our executable our invocation of the component now let's now then actually write a more complicated target so we will get rid of some of this stuff and I'll tell you what in order to get rid of it will add a new target until make form we can have a target called clean and since it's not the first target the won't automatic be executed and on the next line we put a tab and then we can actually say RM Starro so we'll get rid of all the object files if we run make clean we can then execute that target by targeting my plate which removes all the object file we might want to remove hello as well if we're cleaning hello I just type make one make clean there we go now so let's create something sensible in our func dot see what we're going to do is create what's called a header file so here we're now looking at the intersection between make files and spreading our program out remember whenever we want to use something like printf we need to include standard IOH well we're gonna use our function hello dot hello underscore font in our program so we need to create a header file for hello func so that we can declare that function before we use it so this is going to include want door H so where our function is defined it's going to include underage then in front of H we then define our function or so we declare a function and a function is a void function called a low func it took no arguments and what we will also do here is include standard IOH since that is required by our policy program you know because we're using printf so now we have func toss in front page in our hello dot C we will now include front page and because we've including it included it it knows about that function now and we can call hello but we need to edit the make file here there's a couple of different ways we can do this so we can say that our program hello depends on hello dot C and depends on functor oh and because we have an implicit rule for fungi oh and there's an implicit rule for dot C files then we should that should be enough actually to build the program and what you can see it's happened here when I start make is the first thing that happens is we use a dot C flag to see C to come to compile this object file font i/o from fondo C and then C C is invoked with hello dot C and the object file to produce our output for hello and if we now invoke hello we can see that we print out this a test program and we print out the function that we called if we did not include front door H so if I just comment that out then we get implicit declaration of function so the function didn't know what the function was same as if we didn't include standard IOH so that's what header files are for and header files called include just the declarations of all of the functions that are defined in the corresponding C file so that's just a function with any parameters but with no body says no bracket after it well it's in the actual place where the function is defined we've obviously got the body does that make sense so when we compile func see we get this font Oh which then essentially just contains this function which we can then use in our hello dot C main function main program and call and the only distinction in those two causes - C is used to compile an object file we can then just to finish this off we could explicitly have targets here so we could say that hello depends on hello dot C in front dollar and we could say that the target for hello let's actually use clang directly is to compile hello dot C along with fondo and create an output called our program and then we'll have a separate target for funko so will create an explicit target even though an implicit one already exists and we will say we're gonna call clang punk dog we need use the dash C I'm going to create a sorry I'm fun got C I'm gonna create output quad from Drive so let's have a look what happens when we called make wealth since funko already existed we didn't call that we didn't call this target so let us actually either modify funder a func dot C or remove fund obviously in fact what we would want to do is to clear front Oh depends on font dropsy and front page so by saying that we're saying that if any of these two files change then we're going to retain power fund or gonna recompile this target so if I now go and change funk by H and I'll just have the comment in here anything we do to change that file will cause it to be rebuilt now you can see what's happened is funkdoc c has been rebuilt because of that and we've invoked the rebuilding of our target hello and we've produced an output cord of our program our program exists and it prints out the same thing as before I know there's quite a lot to take in there but hopefully you kind of understand what's going on here so this is a target these are its prerequisites each of its prerequisites a bit a checked in turn so if there was no if we didn't have an explicit rule here there would be an implicit rule to build hello dot C but there is an explicit rule so this is what's going to be executed this target is checked front but oh we go down here we'll check its prerequisites if any of this file these files are changed we execute this also because it's a file there is an implicit target for it so we don't we would we don't actually have to specify this but there is a reason why we might want to specify this you can imagine that you might be building a particular object file and you might want to turn optimization on or off for that particular file or have some particular way that you want to compile a you know some special thing that you want to do where you want to do that so that's the very first basic introduction to make falls so it looks like we're actually going to talk quite a lot about splitting up files and that it's complimentary to talking about make files that seems so you can split your program up into as many files as you want and you can separate it out kind of logically you will always need if you want to create an executable program you one of your files will have to have one and only one of your files will have to have the main function because that's the function which is executed when your executable program is ran the rest of the files will not have a main function but they all have other function definitions so if you want to put of your all your numerical functions into a file then these let's just call it num for brevity you will have a file called num Topsy which will contain all of those functions it will contain the definitions of those functions and it's everything required about those functions to run them the body all of the code for them and you'll have a fire cord you will have a file looking right called nut got H which is the header file for the C file which will contain all of the declarations then you will when you have your main dot C or whatever C file contains your main function you can then include this header file and include the declarations of those functions that are defined in num fussy and use them and then what happens once you use them you compile name dot C along with your object file is created when you compile no F dot C just gives you a nun photo and then you produce your output and you can put as many object files as you want you can link a link against as many object files as you want so these are called include files the headers so they're included and they provide definitions for functions these are called object files in a way you can think of these as a little library like when you compile non-stop see all of those functions are essentially available in static the library well that's a special and a library called an object file but when we link against external libraries in the next episode you'll see that it's actually there they're actually reasonably similar and also our own libraries will be a process of bundling up a whole bunch of object files into it at the time the file that we can then link against the compilation step and the reason this is called linking is because we are well in this stage in this step actually we're compiling may not see an linking but traditionally that was separated out into two steps and it will be performed as two steps by the compiler we can parlay nopsi and then we're linking this object file with this function to get the end program and it's called linking because we're kind of linking together all the disparate elements of our program some of the functions are over in this bit some of the functions are in some other object file and we're linking them all together so we can actually have as many object files as we want in fact we can imagine a scenario where we've got tons of different object files because we split up program up into lots of different parts like it might not just be function like a particular set of functions in numerical functions in an object file you might have a separate C file for a different kind of hierarchical or structural parts of your program that make kind of semantic sense so imagine if we were trying to represent a car for example just to give a crude example we might have a function called engine function a file code engine dot C which contains all the functions to do with that and suspension dot C and chassis dot C and so on and then we might have some other files that tie that together you see what I'm saying like you might want to break it up like that and once you start writing programs it kind of the program will start getting too big and you kind of feel like I need to break this up I need to make this more easy to manage like easier to reason about you're sort of naturally you know how to do it so imagine that we'd we compiled these as a dot B dot o and C dot o and then we had some card oxy which contained our main function then we can we might compile this with CC card see a dou V dou Z dou output car so all of our object files will be passed into the compilation command to produce our output so it's quite simple for each file that you want to put functions in put the functions in there put their declarations in a header file and then link them all at the end to the function the program in which you're using those functions of course Eden car dot C we're going to include the header files for each of these it would probably include any gender age suspension dot H or Shishido H whatever a header files we need to include before the files up for the functions return to use hopefully that makes sense ah let's yeah okay so going back to make files when we have a lot of object files like this it can start becoming annoying to have to keep writing them out like to have to write out any WOC do especially when there if we actually use the longer names like engine dotto suspension dough that were created so luckily make file support variables so we can say right variable equals 800 beedo sido and then we can say CC you know target rule we might say CC car dot C dollar sign Brack variable Oh car so we can reference variables like that and of course the variable has to be defined before we can use it and I'll make from so in order to explore the idea of having multiple files what we're going to do is we'll create a file called a dot C and this is going to include a file called a dot H and it's going to contain a single function which is actually just gonna be called a and it's going to print out ima and of course we need a an H rather than a C and we just need to write declaration for this function so called a and I want to increase the number of files that we've got until its copy a dot C to B dot C let's copy a got C to C dot C C this copy a your age to B dot H Seto age and to do age now if we list dot C files you can see we've got a b and c in d and will create a file called multi dot C and in this one I'm gonna include standard IOH we don't necessarily have to because our rather far our other include files are going clued it themselves but the thing with including files is for things like standard Oda H they will only be included once there are ways to make your file B own included ones as well which is a problem that you will encounter once you start working with lots of files so we're going to include all four of our header files we're going to have a main function that is just going to call each of the functions so if I open B dot C we'll make this so it prints out a and B we obviously need to modify B dot H so that it only contains that function declaration now if we open CC it's going to contain include C - H it's gonna say IMC and then we obviously need C - H as the definition for the function C that we've defined in C CC in DC we're gonna have the function D and it's gonna say I am D and include H and then H that's all the files and now we need don't make fog now because we've already got a make file for some of the other examples what we can do make allows you to use a dash F to specify the make file it's quite rare that you would use that usually you have a single make file you know given directory but because we can use that we can then create a file called make file multi and what we'll do is we're gonna have our target for multi and we know that there's already implicit rules so we can just say it depends on 800 B do C do your own and what we will do is because we don't want to cut those out again we're going to use a variable so I'm going to say that the objects are these and so multi depends on multi dot C and the object so we're using a variable and the target for it is going to be playing or cc or GCC whatever you want and we're not only using the automatic variables because we can automatically refer to all of these using a special variable we're not gonna do that for now so we're going to silver right multi dot C but we're not going to write all the objects out again we're just going to do that and the output program is going to be called multi prong so if we're gonna call our output program multi prog and so that make works properly we should actually make our target multi prong because it will actually look for that target look for that existence of that file so there we go and we're not we don't need to bother with targets for these because we know that they'll implicitly be real so let's see what happens when we invoke this make file multi now you can see that all the dependencies have been created first well these of for object files and then of course we're invoking clang men with our multi dot C are for object files and producing a program if we now run the program we print out the four functions our four functions were distributed over four different files and as you can see it's pretty straightforward the last thing that I want to talk about is what's called automatic variables so here we have a an explicit variable yeah so is it no an automatic variable if you will now what you'll notice is that I mean the variable is allowing us to then substitute it here in here so we don't have to write these object files out again and again but what you'll notice is that we're writing out multi dot C twice and that our output name is quite often the same name as the target so the automatic variables allow you to reference things like the target name and it allows you to reference a pre-requisite list so we can actually rewrite this like this targets the same obviously and this is all gonna be the same you can't use automatic variables in the prerequisites we've still got a tab of course and CC but instead of writing that out again we use dollar sign and then a caret and that means all of the prerequisites separated by spaces and then for output what we can use is another one dollar sign Act which means the target name so this can become very useful when you put lots of object files and so on just to shorten the what the rules look like and you can also use make in a more advanced way to define generic rules so like you can say if I'm operating on an archive file unzip this thing or whatever you know that you can define generic rules based based on file extension and so on and then these come in quite handy because you essentially need that and follow generic rule because you don't know what the name of the objects are gonna be so what I'm gonna do is for to show the automatic variables we're also going to do a nested or kind of hierarchical make file at the same time so we need to create a make file I'm just going to get rid of this bin so we let it when you do make fall would make fantastic and over here we're going to create a new file called be nested dot C and in be nested dot C we're going to include be nested h/h and we are going to have this function called be nested be nested is going to call B C and D so for it to call BCD it will need be to H see the ocean data age know what those functions are called then in be nested dot H we're just going to have our function defined right now we're going to create a program called H we need our be nested age I need a do H our main function is going to call a and then it's gonna be called be nested which we know in turn is going to call BC and D so I'm a make file will have off target for nested and it's going to depend on a bunch of objects and what you'll notice here is actually this is going to depend on all of the objects because that's where the functions are we need to include all of the objects which the functions are in either so nested be nested oh when it's compiled although it uses a b and c sort of be C&D it doesn't define them so we still need to include these objects to link against nested dot C and V nested and so that's our they're actually all our objects and we'll say this obviously depends on listed see and it depends on the objects and then the target will call plaing and we're going to use our automatic variable here then to say all of the prerequisites and then our output is going to be this name here to compile it now since these are all object files they will actually all be compiled implicitly let's test that out all right call make the nested make fall notes you see here is then that these were all compiled so even be nested oh was created from be nested dot C and there was no complaints about the not being able to see these functions because we didn't we didn't all we needed to see was the declarations of those functions when compiling be nested oh so we're not linking against that object that object will have unresolved symbols in it what that means is the function that we call BC and D at this point are unresolved they all get resolved and linked together in our final linking step compilation and linking step we're in nested or C when we call be nested in be nested oh we will realize that we were calling BC and D and then all search for all of the objects that passed a clang and it will find them in here those functions so it will resolve them okay so that wasn't actually really a home I thought it was in the end and so we've just you know two missing variables so the last thing that I want to look at is if you want to create your own pattern rules to do sort of custom actions and Falls and to do that I'll just illustrate for example what the rule for compiling a/c fathers so we use a % and this is going to match the fire files in the directory so actually everything to the left of this : because the target is kind of match files and directories so this would be the this is the pattern matching rule for the implicit rule from that for compiling C files and it says so this one's quite straightforward and it will it will look for this stem try and match a file just in fact now of course if you think about what's happening here we always need something like an actual target so if we had a default target called hello it will look them for a file called hello because this is going to be replaced by the target if it can't find a file called hello it looks through the prerequisites so it looks for a file called hello dot see if it can find hello dot C then execute the target and it will compile essentially target hello because this is our output using the automatic variable % app and the input to C C is going to be all of the prerequisites and give this case there's only one and that will actually then produce the target and by the way you look up in there if you look up online you get to find all the automatic variables you can also so let's look at the one from the dot o files it's quite similar so we'll just using CC - see all of the C files and then the output file is could have even target again so how this one won't work I mean imagine in our target if we had 800 as a target or as a dependency of some other target then it would look here and this will match 800 will match and this will stem will be substituted for a and then we'll look for a dollar seeing a local directory that exists we know we can build this and that will produce a duo so that's how the that's two examples of rules pattern rules and that is the implicit pattern rules that actually exist and allow the implicit building of C files and object targets let's create another one and in my directory on the computer I have a file txt file let's call that X dot txt and what we want to do we want to create another file called X dot X dot end which is just the last ten lines of the file so what we do is we say so stem dot txt don't end and it's gonna be stem dot txt we need our tab and what we're going to do is we're going to cat the input we're gonna pipe it into tail I'm gonna do an end take ten lines and then we're just going to redirect this this is all on one line into the target so we're just taking the last ten lines and so what this will do is when we tried to build X text our end it will look for it it will match this rule X will be substituted for the % and then it will look in the directory of X dot text ie the input if the input exists then we perform this role which will produce the output so that's how you can use custom rules perform custom actions on targets arbitrary targets and arbitrary things you want to build so we're going to look at custom patterns first of all I need a file a text file to modify and we're just going to take the last 10 lines of it so let's put something in here we can easily examine the alphabet in it and so what we're gonna do is are we have a single default target and the target is going to build X dot txt dot end and the pattern for this is gonna look for a stem called X dot txt or end so let's go look for a file the matches that pattern and if that doesn't exist it's going to look for the prerequisites and it's going to look for a stem X dot text if it finds anything so not X dot X pattern dot text if it finds anything that matches app which will of course match X dot text it will then it will then build our output and our output is going to cat the input put it pipe it through tail take the last ten lines and then output it to the target okay that's it so now for the core mate with this new make file custom you can see that what it's done is created our target file and it wouldn't cat eggs dog taster and we see that is taking the last ten lines so it worked as we expected so what's worth mentioning here because we were looking for that target and this Pat pattern matched we then determined what prerequisite was going to look for because it's matched an X so then we looked for the file X dot txt okay that's what I want to say about make false this is a sort basic introduction next time we're gonna look at libraries external libraries and creating our own libraries and I think the and that will also help will be looking main files again then alright well thank you for watching and I'll see you next time
Info
Channel: Ashley Mills
Views: 26,849
Rating: 4.9470201 out of 5
Keywords: makefiles, make, c programming, gnu make
Id: 8oyQ3ixxDaM
Channel Id: undefined
Length: 50min 56sec (3056 seconds)
Published: Tue Dec 15 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.