LinuxQuestions.org
Register a domain and help support LQ
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Computer Mad Science: The Anatomy Of A Makefile Part 1

Posted 01-28-2012 at 02:17 PM by rainbowsally

Features:
  • Anatomy of a Makefile

Requires:
  • an editor that can produce real TABs
  • make
  • And bash (with a 'B')... Not DASH, the Debian default shell, which can cause problems with some makefiles, especially on KDE sytems when trying to link
    libpthreads.

This might be interesting for even non-programmers, just to see how makefiles efficiently work through their dependencies.

[If you're already up to speed on what make does, skip to Part 2 and we'll create a makefile creator that you might just find is perfect for smallish projects that you'd rather not clutter up with a bazillion unnecessary files and sluggish, redundant "configure" runs.]

First let's create an empty makefile named Makefile. It's just a text file.

And run 'make' in that folder with the Makefile.

Here's what we get.
Code:
$> make
make: *** No targets.  Stop.
No big surprises there, but we see that 'make' expects some "targets".

Let's now add "all:" to the makefile and see what happens. (Don't miss that colon after the name.)

[If you get any "missing separator" errors in any of these things, it's almost certainly a missing colon or a missing TAB. In the rules section, which we'll talk about in a sec, the spaces before the commands must be real TAB characters.]

And...
Code:
$> make
make: Nothing to be done for `all'.
We've seen that before in 'make' builds, especially when make is descending into directories containing non-code stuff in a system of files created with "automake".

And again, this is not too surprising. But we notice that apparently "all:" is in fact a "target".

Change the name of "all" to "target" (don't delete the trailing colon) and we get the same notice for the new name, though 'all' is by convention used for the first target which is the start of the 'rules' section and below this first line assignments such as we see at the top of normal makefiles are not allowed.

And what we see if we look at a typical makefile is that there are tons of assignments (this equals that) and that environment variable names are enclosed in parens. So $HOME as written in bash needs to be written $(HOME) in a makefile.

Other than that all the stuff above the rules section appears to be somewhat more forgiving than bash. For example spaces between names and "=" signs and we don't need to quote strings composed of several words.

So there are two 'sections' in the makefiles. The assignments section and the rules section.

So now let's start assigning and creating the "rules" section. And by 'rules' we mean simply that we have to perform a target's "depenecies" before we perform the function defined in the target's commands (if it has any).

Code:
string = Im   doing something  .

all: target

target:
	echo $(string)
And here's what we get.
Code:
$> make
echo Im   doing something  .
Im doing something .
Notice that in addition to echoing the line it also shows the command itself. Since the string contains unusual spaces in it, we can see that anything in the assignments section of the makefile is taken literally as if in quotes.

If you add the apostrophe in "I'm", however, you will indeed have to put it in quotes because make also recognizes quoted string with single and double quotes.

Now let's add some dependencies for our target and see what happens.


Code:
string = "I'm doing something!"

all: target

target: dep1 dep2 dep3
	echo $(string)
	@echo "That's all I can tell you right now."

dep1:
	@echo "My name is 'echo'"

dep2:
	@printf "What are you doing? "

dep3:
	@echo "if you don't mind my asking."
	@echo
I know you can't stand the suspense, but what that does, you'll have to find out by doing it. :-)

What do you think is going on here?
What do the "@" signs do?
How did 'target' know echo's name? :-)
In what order were the various commands executed.

What would happen if you re-organized that so that target's only dependency was dep3 and dep3's only dependency was dep2, and dep2's only dependency was dep1?

Here's that makefile and we'll use a "#" comment which works the same as a bash comment in either section, to tell what we're doing for people who want to do development on this project.

Code:
string = "I'm doing something!"

all: target

target: dep3
	echo $(string)
	@echo "That's all I can tell you right now."

# and let's mix 'em up a bit...

dep1:
	@echo "My name is 'echo'"

dep3:dep2
	@echo "if you don't mind my asking."
	@echo

dep2:dep1
	@printf "What are you doing? "
Now let's get serious. A real makefile usually (always?) works with files.

Let's write the above dialog to a file.

Code:
string = "I'm doing something!"

all: temp.txt

temp.txt: target
	@echo "temp.txt has been created."

target: dep3
	echo $(string)                       >> temp.txt
	@echo "That's all I can tell you right now." >> temp.txt

# and let's mix 'em up a bit...

dep1:
	@echo "My name is 'echo'" >> temp.txt

dep3:dep2
	@echo "if you don't mind my asking."  >> temp.txt
	@echo                                 >> temp.txt

dep2:dep1
	@printf "What are you doing? "  >> temp.txt

# this tag/target will run when we type 'make clean'
clean:
	@rm temp.txt
Type 'make' several times and take a look at temp.txt (How many times did the same text get written to the file? Lots.)

Type 'make clean' to delete the file.

Now change the make file to this and run it several times and then look at temp.txt

Code:
string = "I'm doing something!"

all: temp.txt
	@echo "temp.txt has been created."

temp.txt:
	@echo "My name is 'echo'" >> temp.txt
	@printf "What are you doing? "  >> temp.txt
	@echo "if you don't mind my asking."  >> temp.txt
	@echo                                 >> temp.txt
	@echo $(string)                       >> temp.txt
	@echo "That's all I can tell you right now." >> temp.txt

clean:
	@rm temp.txt
What do you think is going on this time?
Where did the dependecy name "temp.txt" come from?
[It's the file name itself. The name could have included the path as well.]

Why didn't the make file keep writing strings into the file this time?

[Once the file existed there was nothing more that needed to be done for the
'rule' governing temp.txt.]

And that's the basic anatomy of a makefile.

We'll look at how and when a file may be re-created depending on its own dependencies (other files) a bit later. But before we sign off for this session let's try one more experiment.

Keep the line with "all" on it but remove the "echo" line below that.

Type 'make clean'

And then 'make'

No surprises there. Okay, now what do you think will happen if you type 'make' a secondor third time after you 'make clean'?

Starting to look familiar?

Any number of files can be listed on the 'all' line and any tag/target can have any number of dependecies. If they end up recursing, make will warn you.

Generally, *ALL* headers should be considered dependencies of *ANY* C/C++ file in a buildbecause changes in one header can change how a C/C++ file has to deal with other C/C++ files.

The principle of conditional compilation (similar to that last experiment) will allow us to conditionally compile only sources that change unless ANY header does, in which case the only reliable way to rebuild is to rebuild the whole thing.

Conditional or 'incremental' building leads to very fast edit-compile-test cycles which is extremely nice for large projects.

Are you tooled up for C/C++ yet?

We might just do some Computer Rocket Mad Science soon. The cutting edge, after all, is attached to a preferably blunt-ish handle.

:-)
Posted in Uncategorized
Views 11792 Comments 7
« Prev     Main     Next »
Total Comments 7

Comments

  1. Old Comment
    I am still confused with the second last Makefile and the last one: Why the second last keeps writing to temp.txt but the last one does not?
    Could you please explain it more explicitly? Thanks a lot!
    Posted 01-08-2013 at 12:42 AM by yifangt yifangt is offline
    Updated 01-08-2013 at 12:43 AM by yifangt (typo)
  2. Old Comment
    Quote:
    Originally Posted by yifangt View Comment
    I am still confused with the second last Makefile and the last one: Why the second last keeps writing to temp.txt but the last one does not?
    Could you please explain it more explicitly? Thanks a lot!
    I wish I could, vifangt. :-)

    If you're interested in how makefiles work, this probably wasn't the best example.

    The interesting stuff 'make' does is in it's reordering of its procedures based on what comes after the colons in the targets. It's freaking packed with features, most of which nobody uses, but in any case, I can't remember what I was up to with this one.

    If this is really a big deal to you, post here again and I'll dig into this and try to figure out what I was up to and what might have been confusing to you.

    Might be that the second one is outputting to stderr instead of stdout? (first guess).

    Unfortunately my modem lives in windows and my linux lives in linux so this requires a bit more than just switching to a terminal and testing the code. Hope to correct this problem soon.

    :-)
    Posted 01-13-2013 at 08:11 PM by rainbowsally rainbowsally is offline
    Updated 01-13-2013 at 08:15 PM by rainbowsally
  3. Old Comment
    My confusion is that:
    2nd last Makefile (Makefile-b, I am aware the name must be Makefile to work!):
    Code:
    string = "I'm doing something!"
    
    all: temp.txt
    
    temp.txt: target
    	@echo "temp.txt has been created."
    
    target: dep3
    	echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt
    ......
    keeps writing to temp.txt if run "make" multiple times.
    The last Makefile (Makefile-a) does not.
    Code:
    string = "I'm doing something!"
    
    all: temp.txt
    	@echo "temp.txt has been created."
    
    temp.txt:
    	@echo "My name is 'echo'" >> temp.txt
    	@printf "What are you doing? "  >> temp.txt
    	@echo "if you don't mind my asking."  >> temp.txt
    	@echo                                 >> temp.txt
    	@echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt
    Why?
    1) Because Makefile-b use target as "intermediate output"?
    2) Or any other reason I missed?
    Thanks a lot!
    Posted 01-13-2013 at 08:48 PM by yifangt yifangt is offline
  4. Old Comment
    Quote:
    Originally Posted by yifangt View Comment
    My confusion is that:
    2nd last Makefile (Makefile-b, I am aware the name must be Makefile to work!)
    First of all, the makefile doesn't need to have any specific name. You can load any file name with the -f switch, like so:
    Code:
    make -f makefile-b
    Now..

    This was a really great observation, yifangt. And I think your examples are probably better than mine because it shows how real targets (as opposed to "phoney" ones) are also file names.

    See the notes in the makefiles below.

    file: a/Makefile
    purpose: source file
    Code:
    # keeps writing to temp.txt if run "make" multiple times.
    #########################################################
    # 'all' has a dependency on 'temp.txt' and the file exists
    # but temp.txt has additional dependenices on 'target'
    # so 'make' checks to see if 'target' (the file) also 
    # exists, which it can't, because 'target' itself is a 
    # phoney target -- never producing a file by that name.
    # It will always run... unless... ;-)
    #########################################################
    
    
    string = "I'm doing something!"
    
    all: temp.txt
    
    temp.txt: target
    	@echo "temp.txt has been created."
    
    target: # dep3
    	echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt

    file: b/Makefile
    purpose: source file
    Code:
    # does not keeps writing to temp.txt if run "make" multiple times.
    ##################################################################
    # because the file 'temp.txt' exists.  Being the only dependency 
    # for all (either direcly or indirectly), make sees that the file 
    # exists (and all the dependencies are satisfied) so once created, 
    # make is done.
    ##################################################################
    
    string = "I'm doing something!"
    
    all: temp.txt
    	@echo "temp.txt has been created."
    
    temp.txt:
    	@echo "My name is 'echo'" >> temp.txt
    	@printf "What are you doing? "  >> temp.txt
    	@echo "if you don't mind my asking."  >> temp.txt
    	@echo                                 >> temp.txt
    	@echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt

    Try example 'a' above again and remove temp.txt and create a file named 'target' and see if it even creates the first copy of temp.txt. It can't. Because 'all' doesn't produce a file, it expects 'target' to do that.

    Then remove the file named 'target' and try it again. Now it creates 'temp.txt' and keeps on writing to it for the reason that the file 'target' never exists.

    Bottom line: targets are also file names. In fact 'make' also checks creation times to make sure that depenencies and the files that depend on them are 'current'.

    We could attempt to demonstrate that too, but... well... Maybe later. ;-)

    And thank you for your observations and your question.

    :-)
    Posted 01-13-2013 at 10:12 PM by rainbowsally rainbowsally is offline
    Updated 01-13-2013 at 10:36 PM by rainbowsally (copy/paste TABs into file)
  5. Old Comment

    Still writing!

    Thanks!
    I tried your makefile-a. First removed temp.txt, and created an empty file named "target" by:
    Code:
    rm temp.txt; touch target
    Then I ran again:
    Code:
    make -f makefile-a
    but it still keeps wrting to temp.txt. Any clue?

    I tried with another makefile:
    Code:
    # does not write to temp.txt if file "target" exists.
    ############################################  
    #   
    string = "I'm doing something!"
    #    
    all: test_target
    
    test_target: 
    	echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt
    if I run multiple times:
    Code:
    make -f makefile-c
    still new content was added to temp.txt!
    But after I create an empty file "test_target":
    Code:
    touch test_target;
    make -f makefile-c
    make: Nothing to be done for `all'.
    So, it seems to me target of 'all' is the trick. Once there exists a file with the same name of the target for 'all', make will stop writing/adding to the existing file, Am I right?
    Posted 01-14-2013 at 11:21 AM by yifangt yifangt is offline
    Updated 01-14-2013 at 11:41 AM by yifangt
  6. Old Comment

    Still writing!

    Quote:
    Originally Posted by yifangt View Comment
    So, it seems to me target of 'all' is the trick. Once there exists a file with the same name of the target for 'all', make will stop writing/adding to the existing file, Am I right?
    The other targets do the same thing.

    A : B C
    action

    Consider the above.

    Read A "needs B and C" before "action".

    B and C must exist as targets in the makefile but they don't need to produce a file.

    B might be a phony target, like "clean", if you like rebuilding from scratch every time. ;-)

    'clean:' and 'distclean:' are phony targets. They don't create files so they will always run.

    An C might be a filename, or even a library name. If it's a library name, the "action" for C might very well be to cd into another directory and 'make' there.

    And the 'all' in that other makefile is no different from any of the other targets.

    In fact the first target doesn't even really even need to be named "all". It's just named that by convention.

    What would happen if a file named 'all' existed in the same directory with the makefile?

    :-)
    Posted 01-17-2013 at 05:08 AM by rainbowsally rainbowsally is offline
  7. Old Comment
    Hi again. :-)

    About the "still writing" problem. I think there might be something going on with the way linux caches files or your browser might not have refreshed. But try this -- exactly -- and it should behave as expected.

    Create the file below in a new directory named 'a'.

    file: a/Makefile
    purpose: source file
    Code:
    # keeps writing to temp.txt if run "make" multiple times.
    
    string = "I'm doing something!"
    
    all: temp.txt
    
    temp.txt: target
    	@echo "temp.txt has been created."
    
    target: # dep3
    	echo $(string)                       >> temp.txt
    	@echo "That's all I can tell you right now." >> temp.txt
    open a terminal and type:
    Code:
    echo > terminal
    Then CLOSE the terminal. Open a new terminal and run 'make'.

    No "temp.txt".

    It SAYS it created it, but that's because temp.txt (the target) doesn't make the file in this case. "target" (the target) does, but since the file 'target' was up to date, the target named "target" never ran.

    Clear as mud, eh? ;-)
    Posted 01-17-2013 at 06:01 AM by rainbowsally rainbowsally is offline
 

  



All times are GMT -5. The time now is 12:35 PM.

Main Menu
Advertisement

Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration