MESSAGE
DATE | 2008-10-01 |
FROM | Ruben Safir
|
SUBJECT | Subject: [NYLXS - HANGOUT] C Programming Workship: Make
|
============================================================================== C-Scene Issue #2 Multi-file projects and the GNU Make utility Author: George Foot Email: george.foot-at-merton.ox.ac.uk Occupation: Student at Merton College, Oxford University, England IRC nick: gfoot ==============================================================================
Disclaimer: The author accepts no liability whatsoever for any damage this may cause to anything, real, abstract or virtual, that you may or may not own. Any damage caused is your responsibility, not mine.
Ownership: The section `Multi-file projects' remains the property of the author, and is copyright (c) George Foot May-July 1997. The remaining sections are the property of CScene and are copyright (c) 1997 by CScene, all rights reserved. Distribution of this article, in whole or in part, is subject to the same conditions as any other CScene article.
0) Introduction ~~~~~~~~~~~~~~~
This article will explain firstly why, when and how to split your C source code between several files sensibly, and it will then go on to show you how the GNU Make utility can handle all your compilation and linking automatically. Users of other make utilities may still find the information useful, but it may require some adaptation to work on other utilities. If in doubt, try it out, but check the manual first.
1) Multi-file projects ~~~~~~~~~~~~~~~~~~~~~~
1.1 Why use them? ----------------- Firstly, then, why are multi-file projects a good thing? They appear to complicate things no end, requiring header files, extern declarations, and meaning you need to search through more files to find the function you're looking for.
In fact, though, there are strong reasons to split up projects. When you modify a line of your code, the compiler has to recompile everything to create a new executable. However, if your project is in several files and you modify one of them, the object files for the other source files are already on disk, so there's no point in recompiling them. All you need to do is recompile the file that was changed, and relink the object files. In a large project this can mean the difference between a lengthy (several minutes to several hours) rebuild and a ten or twenty second adjustment.
With a little organisation, splitting a project between files can make it much easier to find the piece of code you are looking for. It's simple - you split the code between the files based upon what the code does. Then if you're looking for a routine you know exactly where to find it.
It is much better to create a library from many object files than from a single object file. Whether or not this is a real advantage depends what system you're using, but when gcc/ld links a library into a program at link time it tries not to link in unused code. It can only exclude entire object files from the library at a time, though, so if you reference any symbols from a particular object file of a library the whole object file must be linked in. If the library is very segmented, the resulting executables can be much smaller than they would be if the library consisted of a single object file.
Also, since your program is very modular with the minimum amount of sharing between files there are many other benefits -- bugs are easier to track down, modules can often be reused in another project, and last but not least, other people will find it much easier to understand what your code is doing.
1.2 When to split up your projects ----------------------------------
It is obvisouly not sensible to split up *everything*; small programs like `Hello World' can't really be split anyway since there's nothing to split. Splitting up small throwaway test programs is pretty pointless too. In general, though, I split things whenever doing so seems to improve the layout, development and readability of the program. This is in fact true most of the time.
The decision about what to split and how is of course yours; I can only make general suggestions here, which you may or may not choose to follow.
If you are developing a fairly large project, you should think before you start how you are going to implement it, and create several (appropriately named) files initially to hold your code. Of course, don't hesitate to create new files later in development, but if you do then you are changing your mind and should perhaps think about whether some other structural changes would be appropriate.
For medium-sized projects, you can use the above technique of course, or you might be able to just start typing, and split the file up later when it is getting hard to manage. In my experience, though, it is a great deal simpler to start off with a scheme in mind and stick to it or adapt it as the program's needs change during development.
1.3 How to split up projects ----------------------------
Again, this is strictly my opinion; you may (probably will?) prefer to lay things out differently. This is touching on the controversial topic of coding style; what I present here is simply my personal preference (along with reasons for each of these guidelines): i) Don't make header files which span several source files (exception: library header files). It's much easier to track and usually more efficient if each header file only declares symbols from one source file. Otherwise, changing the structure of one source file (and its header file) may cause more files to be rebuilt that is really necessary.
ii) Where appropriate, do use more than one header file for a source file. It is often useful to seperate function prototypes, type definitions, etc, from the C source file into a header file even when they are not publicly available. Making one header file for public symbols and one for private symbols means that if you change the internals of the file you can recompile it without having to recompile other files that use the public header file.
iii) Don't duplicate information in several header files. If you need to, #include one in the other, but don't write out the same header information twice. The reason for this is that if you change the information in the future you will only need to change it once, rather than hunting for duplicates which would also need modifying.
iv) Make each source file #include all the header files which declare information in the source file. Doing this means that the compiler is more likely to pick out mistakes, where you have declared something differently in the header file to what it is in the source file.
1.4 Notes on common errors --------------------------
a) Identifier clashes between source files: In C, variables and functions are by default public, so that any C source file may refer to global variables and functions from another C source file. This is true even if the file in question does not have a declaration or prototype for the variable or function. You must, therefore, ensure that the same symbol name is not used in two different files. If you don't do this you will get linker errors and possibly warnings during compilation.
One way of doing this is to prefix public symbols with some string which depends on the source file they appear in. For example, all the routines in gfx.c might begin with the prefix `gfx_'. If you are careful with the way you split up your program, use sensible function names, and don't go overboard with global variables, this shouldn't be a problem anyway.
To prevent a symbol from being visible from outside the source file it is defined in, prefix its definition with the keyword `static'. This is useful for small functions which are used internally by a file, and won't be needed by any other file.
b) Multiply defined symbols (again): A header file is literally substituted into your C code in place of the #include statement. Consequently, if the header file is #included in more than one source file all the definitions in the header file will occur in both source files. This causes them to be defined more than once, which gives a linker error (see above).
Solution: don't define variables in header files. You only want to declare them in the header file, and define them (once only) in the appropriate C source file, which should #include the header file of course for type checking. The distinction between a declaration and a definition is easy to miss for beginners; a declaration tells the compiler that the named symbol should exist and should have the specified type, but it does not cause the compiler to allocate storage space for it, while a definition does allocate the space. To make a declaration rather than a definition, put the keyword `extern' before the definition.
So, if we have an integer called `counter' which we want to be publicly available, we would define it in a source file (one only) as `int counter;' at top level, and declare it in a header file as `extern int counter;'.
Function prototypes are implicitly extern, so they do not create this problem.
c) Redefinitions, redeclarations, conflicting types: Consider what happens if a C source file #includes both a.h and b.h, and also a.h #includes b.h (which is perfectly sensible; b.h might define some types that a.h needs). Now, the C source file #includes b.h twice. So every #define in b.h occurs twice, every declaration occurs twice (not actually a problem), every typedef occurs twice, etc. In theory, since they are exact duplicates it shouldn't matter, but in practice it is not valid C and you will probably get compiler errors or at least warnings.
The solution to this problem is to ensure that the body of each header file is included only once per source file. This is generally achieved using preprocessor directives. We will #define a macro for each header file, as we enter the header file, and only use the body of the file if the macro is not already defined. In practice it is as simple as putting this at the start of each header file:
#ifndef FILENAME_H #define FILENAME_H
and then putting this at the end of it:
#endif
replacing FILENAME_H with the (capitalised) filename of the header file, using an underline instead of a dot. Some people like to put a comment after the #endif to remind them what it is referring to, e.g.
#endif /* #ifndef FILENAME_H */ Personally I don't do that since it's usually pretty obvious, but it is a matter of style.
You only need to do this trick to header files that generate the compiler errors, but it doesn't hurt to do it to all header files.
1.5 Rebuilding a multi-file project ----------------------------------- It is important to recognise the distinction here between compiling and linking. A compiler takes C source code and generates some form of object code from that source code, without resolving external references. A linker is then invoked, which takes object file(s) and links them together into an executable file, along with standard libraries and other libraries you may specify. At this stage references in one object file to symbols in another are resolved, and depending on the linker unresolved references may be reported, usually as errors.
The basic procedure, then, is to compile your C source files one by one to object format, and finally link all the object files together, along with any libraries you need. How exactly you do this will depend on your compiler; here I shall describe the commands for gcc, which may also work on your compiler even if it is not gcc.
Note that gcc is a multi-purpose tool which calls other components (preprocessor, compiler, assembler, linker) as required; which of these are called depends upon what the input files are and what switches you give it.
Normally if you pass C source files alone it will preprocess, compile and assemble them one by one, then link to an executable file (usually called a.out) from the resulting objectfiles. This would work in our case, but would destroy many of the benefits of splitting the project up in the first place.
If you pass the -c switch, gcc will compile the listed files to object format only, naming the object files after the C source files, replacing the `.c' or `.cc' suffix with `.o'. If you pass a list of object files, gcc will simply link them to form an executable, again called a.out by default. You can change the name of the output file from either of these by passing the -o switch followed by a filename.
So, after altering a source file you need to recompile it by calling `gcc -c filename.c' and then relink the project by calling `gcc -o exec_filename *.o'. If you alter a header file, you need to recompile all those source files which #include it; you could type `gcc -c file1.c file2.c file3.c' and then relink, for example.
This is, of course, fairly tedious; luckily there are tools available to simplify this process. The second half of this article describes such a tool: the GNU Make utility.
2) The GNU Make utility ~~~~~~~~~~~~~~~~~~~~~~~
2.1 Basic makefile structure ----------------------------
GNU Make's main action is to read through a text file (a makefile) containing (principly) information about which files (`targets') are created from which other files (`dependencies') and what commands should be executed to do this. Armed with this information, make will then look at the files on disk and, if the timestamp on a target is older than that on at least one of its dependencies, make will issue the commands specified in the hope of bringing the target file up to date.
The makefile is normally called (funnily enough) `makefile' or `Makefile', but you can specify other filenames on make's command line. If you don't, it will look for `makefile' or `Makefile' so it's simplest just to use those names.
A makefile consists (mainly) of a sequence of rules of this form:
: ... (tab) (tab) . . .
For example, consider the following makefile:
=== start of makefile === myprog : foo.o bar.o gcc foo.o bar.o -o myprog
foo.o : foo.c foo.h bar.h gcc -c foo.c -o foo.o
bar.o : bar.c bar.h gcc -c bar.c -o bar.o === end of makefile ===
This is a very basic makefile - make starts at the top, and uses the first target, `myprog', as its primary goal (the thing it is ultimately trying to keep up-to-date). The rule tells it that whenever the file `myprog' is older than either `foo.o' or `bar.o', the command on the next line should be executed.
However, before checking the timestamps of foo.o and bar.o it first looks through the makefile for rules with foo.o or bar.o as targets. It finds the rule for foo.o, seeing that it depends on foo.c, foo.h and bar.h. It cannot find additional rules saying how to create any of these files, so it then checks the timestamps on disk. If any of these files are newer than foo.o, the command `gcc -o foo.o foo.c' will be executed, bringing foo.o up to date.
The same check is then made for bar.o, depending upon bar.c and bar.h.
Now make returns to the rule for `myprog'. If either of the other two rules were executed, myprog will need rebuilding (one of the .o files will be newer than `myprog') and so the linking command will be executed.
Hopefully at this stage you can see the benefit of using the make utility to build your programs - all the tedious checking mentioned at the end of the previous chapter is done for you by make, checking the timestamps. A simple change to one of your source files will cause that file to be recompiled (since the .o file depends on the .c file) and then the executable will be relinked (since the .o file has now been modified). The real gain, though, shows if you modify a header file - you no longer need to remember which of your source files depended on it, since the information is all there in the makefile. The make utility will happily recompile any files which are listed as depending on any modified header files, and relink if required.
Of course, this depends on you making sure the rules in the makefile are correct, listing only those header files which are #included in the source file...
2.2 Writing make rules ----------------------
The obvious (and simplest) way to write your rules is by looking at each source file in turn, adding its object file as a target, and the C source file as a dependency along with all the headers it #includes. However, you should also list as dependencies any other headers which are #included by those headers, and any headers they #include, and so on... it gets difficult to track. So is there an easier way?
Of course there is - ask the compiler! It ought to know what headers it would include when compiling each source file. With gcc you can specify the -M switch, and then gcc will send to stdout a rule for each C file you pass, with the object file as a target and the C file and all headers #included therein as dependencies. Note that this rule will include both headers named between angle brackets (`<', `>') and headers named in inverted commas (`"'); it is often a pretty safe bet that the system header files (like stdio.h, stdlib.h, etc) aren't going to change though. If you pass -MM instead of -M to gcc, it will omit any header files whose names were enclosed with angle brackets.
The rule output by gcc won't have a command part; you can either write in your own command, or just leave it and let make use its implicit rule (see section 2.4).
2.3 Makefile variables ----------------------
I wrote earlier that makefiles contain mainly rules. Another thing they can contain are variable definitions.
A variable in a makefile is somewhat like an environment variable; indeed, environment variables are translated into make variables during the make process. They are case sensitive, and normally specified in upper case. They can be referenced almost anywhere, and so they can be used for many purposes, for example:
i) Holding lists of files. In the makefile above, the rule to make the executable contains the object filenames as dependencies, and the same filenames are passed to gcc in the command for that rule. If a variable were used in both cases, adding new object files would be simpler and less prone to error.
ii) Holding executable filenames. If your project is taken to a non-gcc system, or if you just want to use a different compiler, you would have to change all calls to the compiler to use the new name. Using a variable instead means that you need only change the name in one place, and all the commands would be updated.
iii) Holding compiler flags. Presumably you want all your compilation commands to pass the same set of options (e.g. -Wall -O -g); if you put the option list in a variable then you can put the variable in all your compiler calls and just change the options in one place whenever you need to.
To set a variable, you simply write its name at the start of a line, followed by an = sign, and then its new value. To reference a variable later on you write a dollar sign, then the variable name in brackets. For example, here is the previous makefile rewritten using variables: === start of makefile === OBJS = foo.o bar.o CC = gcc CFLAGS = -Wall -O -g
myprog : $(OBJS) $(CC) $(OBJS) -o myprog
foo.o : foo.c foo.h bar.h $(CC) $(CFLAGS) -c foo.c -o foo.o
bar.o : bar.c bar.h $(CC) $(CFLAGS) -c bar.c -o bar.o === end of makefile ===
There are also various automatic variables, which are defined for each rule. Three useful ones are $-at-, $< and $^ (no brackets are needed for these). $-at- expands to the filename of the target of the rule, $< expands to the first dependency in the dependency list, and $^ expands to the entire dependency list (with duplicate filenames removed). Using these, then, we could write the above makefile as:
=== start of makefile === OBJS = foo.o bar.o CC = gcc CFLAGS = -Wall -O -g
myprog : $(OBJS) $(CC) $^ -o $-at-
foo.o : foo.c foo.h bar.h $(CC) $(CFLAGS) -c $< -o $-at-
bar.o : bar.c bar.h $(CC) $(CFLAGS) -c $< -o $-at- === end of makefile ===
There are many other things you can do with variables, especially when you mix them with functions. For further information, see the GNU Make manual.
2.4 Implicit rules ------------------
Note that in that last makefile example the commands to create the .o files are identical. This is hardly surprising since they both achieve similar goals - creating a .o file from a .c file and some others is a standard procedure. In fact, make already knows how to do it - it has built in rules called implicit rules which tell it what to do if you don't put any commands in a rule.
If we remove the commands from the rules for creating foo.o and bar.o, make will fall back on its implicit rule database and should find a suitable command. Its command uses several variables, so you can easily customise it to your tastes; it uses the variable CC to run a compiler (just like we did earlier), passing it the CFLAGS variable for C programs (CXXFLAGS for C++ programs), CPPFLAGS (C preprocessor flags), TARGET_ARCH (don't worry about this), then it puts the flag `-c' followed by the variable $< (first dependency), then the flag `-o' followed by the variable $-at- (the target file). The effective command for C compilation is: $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $-at-
You can define these variables however you like, of course. This should explain why the output from gcc with the -M or -MM switch is suitable for immediate inclusion in a makefile.
2.5 Phony targets -----------------
Suppose you had a project in which two executable files needed to be created. You would want the primary goal to create both files, but independently of each other - if one needs rebuilding, the other may not. To achieve this you can use what is called a phony target. A phony target is just like a normal target, but it is not an actual file on disk. Because of this, make assumes that it needs creating, and always executes any commands in its rule, after bringing its dependencies up to date.
So, if we write at the top of our makefile:
all : exec1 exec2
where exec1 and exec2 are the filenames of our two target executables, make will set this as its primary goal and try to bring `all' up to date on every invocation. Since there are no commands here which will affect a file called `all' on disk, this rule won't actually change the status of `all' at all. However, since the file does not exist make will check that exec1 and exec2 don't need rebuilding, and rebuild them if they are out of date, which is exactly what we wanted to do.
Phony targets can also be used to describe a set of non- default actions. For example, you might want to remove all the files generated by make. To do this, you could make a rule in the makefile like this:
veryclean : rm *.o rm myprog
Provided no rules are listed as depending upon the target `veryclean', this will never be executed. However, if the user types `make veryclean' explicitly, make will use this as its primary goal, and run the rm commands.
What if there is a file on disk called veryclean though? In this case, since this rule has no dependencies, the target `veryclean' must be up to date, and even if the user explicitly asks make to recreate it nothing will happen. The solution here is to declare all your phony targets as .PHONY, telling make not to bother looking for them on disk, not to bother checking implicit rules, and to always assume that the specified target is not up to date. Adding this line to the makefile containing the above rule:
.PHONY : veryclean
would do the trick. Note that this is a special make rule, that make knows .PHONY is a special target, and of course you can put more dependencies in if you like and make will know that they are all phony targets.
2.6 Functions -------------
Functions in makefiles are very similar to variables - to use them, you write a dollar sign, an open bracket, and then the name followed by a space and a comma-separated list of arguments, and lastly a closing bracket. For example, there is a function called `wildcard' in GNU Make which takes one argument and expands into a space- separated list of all files matching the specification given. To use it you could write something like
SOURCES = $(wildcard *.c)
which would create a list of all files ending in `.c' and put it in the SOURCES variable. Of course, you don't have to store the results in variables.
Another useful function is the patsubst function. It takes three parameters - the first is a pattern to match, the second shows what to replace it with, and the third is a space-separated list of words to process. For example, after the variable definition above,
OBJS = $(patsubst %.c,%.o,$(SOURCES))
would take all the words (filenames) in the SOURCES list and for each, if it ends in `.c', it will replace the `.c' with a `.o'. Note that the % symbol matches one or more characters, and the string it matches each time is called the stem. In the second parameter, the % is read as whatever stem it matched in the first parameter.
2.7 A pretty effective makefile -------------------------------
With the information so far we can write quite an effective makefile, which will be able to do most of our dependency checking for us, and will fit most projects without much modification.
Firstly we need a basic makefile which will build the program. We can make it search the current directory for source files, and assume that they are all part of the project, by using a variable SOURCES as above. It is probably wise to also include *.cc, in case the compilation is for C++.
SOURCES = $(wildcard *.c *.cc)
Using patsubst we can then create a list of object files which will be created; if our sources list contains .cc files as well as .c files we'll need to nest calls to patsubst like so:
OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))
The innermost patsubst call will replace the .cc files' suffixes only, forming a list which the outermost patsubst processes, replacing the .c files' suffixes.
Now we can form a rule to build the executable:
myprog : $(OBJS) gcc -o myprog $(OBJS)
Further rules may not be necessary; gcc knows already how to create the object files. Next, we can make a rule to create the dependency information:
depends : $(SOURCES) gcc -M $(SOURCES) > depends
This creates a file called `depends' whenever it does not exist or a source file is newer than the existing depends' file, which contains the rules gcc created for the source files. Now we need to get make to consider these rules as part of the makefile. The technique here is rather like the #include system in C - we simply ask make to include this file in the makefile, like so:
include depends
GNU Make will see this, and check that `depends' is up to date; if it is not, it will recreate it, following the rule we gave. This done, it will include the (new) set of rules and proceed to process the primary goal, `myprog'. On seeing the rule for myprog, it will check all the object files are up to date - using the rules from the `depends' file, which we know is up to date itself.
This system is fairly inefficient, however, since whenever a source file is changed all the source files must be preprocessed again to create the `depends' file, and it isn't 100% safe either since changing a header file will not cause the dependency information to be updated. However, it is quite useful as it stands.
2.8 A more effective makefile -----------------------------
This is a makefile I use for most things I do. It should build most projects without modification. I have used it mainly with djgpp, a DOS port of gcc, so the executable name, `alleg' library, and the RM-F variable reflect this.
=== start of makefile ===
###################################### # # # Generic makefile # # # # by George Foot # # email: george.foot-at-merton.ox.ac.uk # # # # Copyright (c) 1997 George Foot # # All rights reserved. # # # # No warranty, no liability; # # you use this at your own risk. # # # # You are free to modify and # # distribute this without giving # # credit to the original author. # # # ######################################
### Customising # # Adjust the following if necessary; EXECUTABLE is the target # executable's filename, and LIBS is a list of libraries to link in # (e.g. alleg, stdcx, iostr, etc). You can override these on make's # command line of course, if you prefer to do it that way.
EXECUTABLE := mushroom.exe LIBS := alleg
# Now alter any implicit rules' variables if you like, e.g.:
CFLAGS := -g -Wall -O3 -m486 CXXFLAGS := $(CFLAGS)
# The next bit checks to see whether rm is in your djgpp bin # directory; if not it uses del instead, but this can cause (harmless) # `File not found' error messages. If you are not using DOS at all, # set the variable to something which will unquestioningly remove # files.
ifneq ($(wildcard $(DJDIR)/bin/rm.exe),) RM-F := rm -f else RM-F := del endif
# You shouldn't need to change anything below this point.
SOURCE := $(wildcard *.c) $(wildcard *.cc) OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE))) DEPS := $(patsubst %.o,%.d,$(OBJS)) MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS)) MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \ $(patsubst %.d,%.cc,$(MISSING_DEPS))) CPPFLAGS += -MD
.PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean : -at-$(RM-F) *.o -at-$(RM-F) *.d
veryclean: clean -at-$(RM-F) $(EXECUTABLE)
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),) $(MISSING_DEPS) : -at-$(RM-F) $(patsubst %.d,%.o,$-at-) endif
-include $(DEPS)
$(EXECUTABLE) : $(OBJS) gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))
=== end of makefile ===
A few things are worth explaining about this. Firstly, I have defined most of my variables using := instead of =. The effect of this is to immediately expand all function and variable references in the definition. With =, the function and variable references are left alone, meaning that changing the value of a variable can change other variables' values. For example:
A = foo B = $(A) # Now B is $(A) which is `foo'. A = bar # Now B is still $(A), but it is now `bar'. B := $(A) # B is now `bar'. A = foo # B is still `bar'.
After a # symbol make ignores any text until the end of the line.
The ifneq...else...endif system is a way of conditionally disabling/enabling parts of a makefile. ifeq takes two parameters. If they are equal, it includes the portion of the makefile up to the else (or endif, if there is no else); if not, it includes the portion between else and endif if the else is present. ifneq is exactly the opposite.
The filter-out function takes two space-separated lists, and expands to the second list with all members of the first list removed. I have used it here to take the DEPS list and remove all members which exist, leaving behind any which are missing.
The CPPFLAGS as I mentioned earlier contains flags to pass to the preprocessor in implicit rules. The -MD switch is like -M, but the information is sent to a file whose name is formed by removing the .c or .cc from the source file and replacing it with a .d (which explains why I form the DEPS variable that way). The files mentioned in DEPS are included in the makefile later on using `-include', which suppresses any errors if the files are not found on disk.
If any dependency files are missing, the makefile will remove the corresponding .o file from disk as well, causing make to rebuild it. Since CPPFLAGS specifies -MD, the .d file will be recreated too.
Lastly, the addprefix function expands to the list given in its second parameter, with its first parameter prepended to each word of the list.
The targets of this makefile (which can be passed on the command line to select them) are:
everything (default): Update the main executable, also creating or updating a `.d' file and a `.o' file for each source file.
deps: Just create/update a `.d' file for each source file.
objs: Create/update the `.d' files and the object files for each source file.
clean: Delete all the intermediate/dependency files (*.d and *.o).
veryclean: Do `clean' and also delete the executable.
rebuild: Do `veryclean' and `everything'; i.e. rebuild from scratch
Of these, clean, veryclean and rebuild are the only really useful ones apart from the default of everything.
I am not aware of any way in which this makefile can fail, given a directory of source files, unless the dependency files have been mangled. If this does occur, simply typing `make clean' should fix the problem by removing all the dependency and object files. It's best not to mess around with them, of course. If you see a way this makefile could fail to do its job, please do let me know so that I can fix it.
3 In conclusion ~~~~~~~~~~~~~~~
I hope this article has explained clearly enough how multi- file projects work, and has shown how to use them in a way which is logical and safe. You should be able to use the GNU Make utility well enough now to manage small projects, and if you understood what was written in the later sections you should not have any trouble with it.
GNU Make is a powerful tool, and although it was designed primarily for building programs in this way it has many other uses. For more information on the utility, its syntax, functions, and other features, you should (as with any GNU tool) consult the info pages about it.
C Scene Official Web Site : http://cscene.oftheinter.net C Scene Official Email : cscene-at-mindless.com This page is Copyright © 1997 By C Scene. All Rights Reserved
-- http://www.mrbrklyn.com - Interesting Stuff http://www.nylxs.com - Leadership Development in Free Software
So many immigrant groups have swept through our town that Brooklyn, like Atlantis, reaches mythological proportions in the mind of the world - RI Safir 1998
http://fairuse.nylxs.com DRM is THEFT - We are the STAKEHOLDERS - RI Safir 2002
"Yeah - I write Free Software...so SUE ME"
"The tremendous problem we face is that we are becoming sharecroppers to our own cultural heritage -- we need the ability to participate in our own society."
"> I'm an engineer. I choose the best tool for the job, politics be damned.< You must be a stupid engineer then, because politcs and technology have been attached at the hip since the 1st dynasty in Ancient Egypt. I guess you missed that one."
© Copyright for the Digital Millennium
|
|