Skip to content

Instantly share code, notes, and snippets.

@crsuarez
Forked from isaacs/Makefile
Created April 20, 2018 18:09
Show Gist options
  • Save crsuarez/a1501f78e9bcc42f5299b28eda881d95 to your computer and use it in GitHub Desktop.
Save crsuarez/a1501f78e9bcc42f5299b28eda881d95 to your computer and use it in GitHub Desktop.

Revisions

  1. @isaacs isaacs revised this gist Jan 15, 2015. No changes.
  2. @isaacs isaacs revised this gist Jan 15, 2015. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions Makefile
    Original file line number Diff line number Diff line change
    @@ -114,6 +114,8 @@ result.txt: source.txt
    #
    # $^ This is the list of ALL input files, not just the first one.
    # You can remember it because it's like $<, but turned up a notch.
    # If a file shows up more than once in the input list for some reason,
    # it's still only going to show one time in $^.
    #
    # $? All the input files that are newer than the target
    # It's like a question. "Wait, why are you doing this? What
    @@ -220,6 +222,8 @@ destination: $(destfiles)
    # posix system because cats are wonderful and a core part of UNIX.

    kitty: $(destfiles)
    @# Remember, $< is the input file, but $^ is ALL the input files.
    @# Cat them into the kitty.
    cat $^ > kitty

    # Note what's happening here:
  3. @isaacs isaacs revised this gist Jan 15, 2015. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion Makefile
    Original file line number Diff line number Diff line change
    @@ -112,6 +112,9 @@ result.txt: source.txt
    # pipe in bash. `head <foo.txt` is using the contents of
    # foo.txt as the input. Also the < points INto the $
    #
    # $^ This is the list of ALL input files, not just the first one.
    # You can remember it because it's like $<, but turned up a notch.
    #
    # $? All the input files that are newer than the target
    # It's like a question. "Wait, why are you doing this? What
    # files changed to make this necessary?"
    @@ -217,7 +220,7 @@ destination: $(destfiles)
    # posix system because cats are wonderful and a core part of UNIX.

    kitty: $(destfiles)
    cat $(destfiles) > kitty
    cat $^ > kitty

    # Note what's happening here:
    #
  4. @isaacs isaacs revised this gist Jan 15, 2015. 1 changed file with 78 additions and 45 deletions.
    123 changes: 78 additions & 45 deletions Makefile
    Original file line number Diff line number Diff line change
    @@ -79,13 +79,13 @@ result.txt: source.txt
    # @echo "building source.txt"
    # echo "this is the source" > source.txt
    #
    # Run `make result.txt` and you'll see it first creates source.txt, and then
    # copies it to result.txt. Try running `make result.txt` again, and you'll see
    # that nothing happens! That's because the dependency, source.txt, hasn't
    # changed, so there's no need to re-build result.txt.
    # Run `make result.txt` and you'll see it first creates source.txt, and then
    # copies it to result.txt. Try running `make result.txt` again, and you'll see
    # that nothing happens! That's because the dependency, source.txt, hasn't
    # changed, so there's no need to re-build result.txt.
    #
    # Run `touch source.txt`, or edit the file, and you'll see that
    # `make result.txt` re-builds the file.
    # Run `touch source.txt`, or edit the file, and you'll see that
    # `make result.txt` re-builds the file.
    #
    #
    # Let's say that we were working on a project with 100 .c files, and each of
    @@ -103,36 +103,62 @@ result.txt: source.txt
    # file and the output file. Here are the special variables:
    #
    # $@ The file that is being made right now by this rule (aka the "target")
    # You can remember this because it's like the "$@" list in a
    # shell script. @ is like a letter "a" for "arguments.
    # When you type "make foo", then "foo" is the argument.
    #
    # $< The input file (that is, the first prerequisite in the list)
    # You can remember this becasue the < is like a file input
    # pipe in bash. `head <foo.txt` is using the contents of
    # foo.txt as the input. Also the < points INto the $
    #
    # $? All the input files that are newer than the target
    # It's like a question. "Wait, why are you doing this? What
    # files changed to make this necessary?"
    #
    # $$ A literal $ character inside of the rules section
    # More dollar signs equals more cash money equals dollar sign.
    #
    # $* The "stem" part that matched in the rule definition's % bit
    # You can remember this because in make rules, % is like * on
    # the shell, so $* is telling you what matched the pattern.
    #
    # You can also use the special syntax $(@D) and $(@F) to refer to just the dir
    # and file portions of $@, respectively. $(<D) and $(<F) work the same way
    # on the $< variable.
    # You can also use the special syntax $(@D) and $(@F) to refer to
    # just the dir and file portions of $@, respectively. $(<D) and
    # $(<F) work the same way on the $< variable. You can do the D/F
    # trick on any variable that looks like a filename.
    #
    # So, our rule for result.txt could've been written like this instead:
    # There are a few other special variables, and we can define our own
    # as well. Most of the other special variables, you'll never use, so
    # don't worry about them.
    #
    # So, our rule for result.txt could've been written like this
    # instead:

    result-using-var.txt: source.txt
    @echo "buildling result-using-var.txt using the $$< and $$@ vars"
    cp $< $@

    # Let's say that we had 100 source files, that we want to convert into 100
    # result files. Rather than list them out one by one in the makefile, we can
    # use a bit of shell scripting to generate them, and save them in a variable.
    # Let's say that we had 100 source files, that we want to convert
    # into 100 result files. Rather than list them out one by one in the
    # makefile, we can use a bit of shell scripting to generate them, and
    # save them in a variable.
    #
    # Note that make uses := for assignment instead of =
    # Also, usually you'd use `$(wildcard src/*.txt)` instead, since probably the
    # files would already exist in your project. Since this is a tutorial, though
    # we're going to generate them using make.

    # This will execute the shell and run the program to generate a list of files.
    # I don't know why that is. The sooner you accept that this isn't
    # bash/sh, the better.
    #
    # Also, usually you'd use `$(wildcard src/*.txt)` instead, since
    # probably the files would already exist in your project. Since this
    # is a tutorial, though we're going to generate them using make.
    #
    # This will execute the shell program to generate a list of files.
    srcfiles := $(shell echo src/{00..99}.txt)

    # How do we make a text file in src?
    # How do we make a text file in the src dir?
    # We define the filename using a "stem" with the % as a placeholder.
    # What this means is "any file named src/*.txt"
    # What this means is "any file named src/*.txt", and it puts whatever
    # matches the "%" bit into the $* variable.
    src/%.txt:
    @# First things first, create the dir if it doesn't exist.
    @# Prepend with @ because srsly who cares about dir creation
    @@ -146,14 +172,14 @@ src/%.txt:


    # To not have to run make for each file, we define a "phony" target that
    # depends on all of the srcfiles, and has no other rules. It's good practice
    # to define your phony rules in a .PHONY declaration in the file. (See the
    # .PHONY entry at the very bottom of this file.)
    # depends on all of the srcfiles, and has no other rules. It's good
    # practice to define your phony rules in a .PHONY declaration in the file.
    # (See the .PHONY entry at the very bottom of this file.)
    #
    # Running `make source` will make ALL of the files in the src/ dir. Before it
    # can make any of them, it'll first make the src/ dir itself. Then it'll copy
    # the "stem" value (that is, the number in the filename matched by the %) into
    # the file, like the rule says above.
    # Running `make source` will make ALL of the files in the src/ dir. Before
    # it can make any of them, it'll first make the src/ dir itself. Then
    # it'll copy the "stem" value (that is, the number in the filename matched
    # by the %) into the file, like the rule says above.
    #
    # Try typing "make source" to make all this happen.
    source: $(srcfiles)
    @@ -162,18 +188,20 @@ source: $(srcfiles)
    # So, to make a dest file, let's copy a source file into its destination.
    # Also, it has to create the destination folder first.
    #
    # The destination of any dest/*.txt file is the src/*.txt file with the
    # matching stem. You could just as easily say that %.css depends on %.styl
    # The destination of any dest/*.txt file is the src/*.txt file with
    # the matching stem. You could just as easily say that %.css depends
    # on %.styl
    dest/%.txt: src/%.txt
    @[ -d dest ] || mkdir dest
    cp $< $@

    # So, this is great and all, but we don't want to type `make dest/#.txt`
    # 100 times!
    #
    # Let's create a "phony" target that depends on all of the destination files.
    # We can use the built-in pattern substitution "patsubst" so we don't
    # have to re-build the list. This uses the same "stem"
    # Let's create a "phony" target that depends on all the destination files.
    # We can use the built-in pattern substitution "patsubst" so we don't have
    # to re-build the list. This patsubst function uses the same "stem"
    # concept explained above.

    destfiles := $(patsubst src/%.txt,dest/%.txt,$(srcfiles))
    destination: $(destfiles)
    @@ -183,9 +211,10 @@ destination: $(destfiles)
    # if the file named "destination" exists if we have something that depends
    # on it later.
    #
    # Let's say that all of these dest files should be gathered up into a proper
    # compiled program. Since this is a tutorial, we'll use the venerable feline
    # compiler called "cat", which is included in every posix system.
    # Let's say that all of these dest files should be gathered up into a
    # proper compiled program. Since this is a tutorial, we'll use the
    # venerable feline compiler called "cat", which is included in every
    # posix system because cats are wonderful and a core part of UNIX.

    kitty: $(destfiles)
    cat $(destfiles) > kitty
    @@ -205,27 +234,31 @@ kitty: $(destfiles)
    #
    # Note that it is smart enough to re-build JUST the single destfile that
    # corresponds to the 25.txt file, and then concats them all to kitty. It
    # *doesn't* re-generate EVERY source file, and then EVERY dest file, every time
    # *doesn't* re-generate EVERY source file, and then EVERY dest file,
    # every time


    # It's good practice to have a `test` target, because people will come to your
    # project, and if there's a Makefile, then they'll expect `make test` to do
    # something.
    # It's good practice to have a `test` target, because people will come to
    # your project, and if there's a Makefile, then they'll expect `make test`
    # to do something.
    #
    # We can't test the kitty unless it exists, so we have to depend on that.
    test: kitty
    @echo "miao" && echo "tests all pass!"

    # Last but not least, `make clean` should always remove all of the stuff that
    # your makefile created, so that we can remove bad stuff if anything gets
    # corrupted or otherwise screwed up.
    # Last but not least, `make clean` should always remove all of the stuff
    # that your makefile created, so that we can remove bad stuff if anything
    # gets corrupted or otherwise screwed up.
    clean:
    rm -rf *.txt src dest
    rm -rf *.txt src dest kitty

    # What happens if there's an error!? Let's say you're building stuff, and
    # one of the commands fails. Make will tear down everything, and abort.
    # one of the commands fails. Make will abort and refuse to proceed if any
    # of the commands exits with a non-zero error code.
    # To demonstrate this, we'll use the `false` program, which just exits with
    # a code of 1 and does nothing else.
    badkitty:
    $(MAKE) kitty # The special var $(MAKE) means "the make that's making this"
    $(MAKE) kitty # The special var $(MAKE) means "the make currently in use"
    false # <-- this will fail
    echo "should not get here"

  5. @isaacs isaacs revised this gist Jan 15, 2015. 1 changed file with 16 additions and 19 deletions.
    35 changes: 16 additions & 19 deletions Makefile
    Original file line number Diff line number Diff line change
    @@ -131,25 +131,17 @@ result-using-var.txt: source.txt
    srcfiles := $(shell echo src/{00..99}.txt)

    # How do we make a text file in src?
    # First the folder must exist, so that's a dependency.
    # We define the filename using a "stem" with the % as a placeholder.
    # What this means is "any file named src/*.txt, but build src first"
    src/%.txt: src
    # What this means is "any file named src/*.txt"
    src/%.txt:
    @# First things first, create the dir if it doesn't exist.
    @# Prepend with @ because srsly who cares about dir creation
    @[ -d src ] || mkdir src
    @# then, we just echo some data into the file
    @# The $* expands to the "stem" bit matched by %
    @# So, we get a bunch of files with numeric names, containing their number
    echo $* > $@

    # To build the directory, we'll just create it using mkdir
    src:
    mkdir -p src

    # But wait, won't this build the directory each time?
    #
    # Nope, because Make is smart. In the first run, it sees that the folder
    # doesn't exist, and makes it. Then, it sees that nothing's changed, so it's
    # skipped.
    #
    # Try running `make src/00.txt` and `make src/01.txt` now.


    @@ -166,18 +158,16 @@ src:
    # Try typing "make source" to make all this happen.
    source: $(srcfiles)

    #

    # So, to make a dest file, let's copy a source file into its destination.
    # Also, it has to create the destination folder first.
    #
    # The destination of any dest/*.txt file is the src/*.txt file with the
    # matching stem. You could just as easily say that %.css depends on %.styl
    dest/%.txt: src/%.txt dest
    dest/%.txt: src/%.txt
    @[ -d dest ] || mkdir dest
    cp $< $@

    dest:
    mkdir -p dest

    # So, this is great and all, but we don't want to type `make dest/#.txt`
    # 100 times!
    #
    @@ -232,4 +222,11 @@ test: kitty
    clean:
    rm -rf *.txt src dest

    .PHONY: source destination clean test
    # What happens if there's an error!? Let's say you're building stuff, and
    # one of the commands fails. Make will tear down everything, and abort.
    badkitty:
    $(MAKE) kitty # The special var $(MAKE) means "the make that's making this"
    false # <-- this will fail
    echo "should not get here"

    .PHONY: source destination clean test badkitty
  6. @isaacs isaacs created this gist Jan 15, 2015.
    235 changes: 235 additions & 0 deletions Makefile
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,235 @@
    # Hello, and welcome to makefile basics.
    #
    # You will learn why `make` is so great, and why, despite its "weird" syntax,
    # it is actually a highly expressive, efficient, and powerful way to build
    # programs.
    #
    # Once you're done here, go to
    # http://www.gnu.org/software/make/manual/make.html
    # to learn SOOOO much more.

    # To do stuff with make, you type `make` in a directory that has a file called
    # "Makefile". You can also type `make -f <makefile>` to use a different
    # filename.
    #
    # A Makefile is a collection of rules. Each rule is a recipe to do a specific
    # thing, sort of like a grunt task or an npm package.json script.
    #
    # A rule looks like this:
    #
    # <target>: <prerequisites...>
    # <commands>
    #
    # The "target" is required. The prerequisites are optional, and the commands
    # are also optional, but you have to have one or the other.
    #
    # Type "make" and see what happens:

    tutorial:
    @# todo: have this actually run some kind of tutorial wizard?
    @echo "Please read the 'Makefile' file to go through this tutorial"

    # By default, the first target is run if you don't specify one. So, in this
    # dir, typing "make" is the same as typing "make tutorial"
    #
    # By default, make prints out the command before it runs it, so you can see
    # what it's doing. This is a departure from the "success should be silent"
    # UNIX dogma, but without that default, it'd be very difficult to see what
    # build logs etc are actually doing.
    #
    # To suppress the output, we've added @ signs before each line, above.
    #
    # Each line of the command list is run as a separate invocation of the shell.
    # So, if you set a variable, it won't be available in the next line! To see
    # this in action, try running `make var-lost`

    var-lost:
    export foo=bar
    echo "foo=[$$foo]"

    # Notice that we have to use a double-$ in the command line. That is because
    # each line of a makefile is parsed first using the makefile syntax, and THEN
    # the result is passed to the shell.
    # Let's try running both of the commands in the *same* shell invocation, by
    # escaping the \n character. Run `make var-kept` and note the difference.

    var-kept:
    export foo=bar; \
    echo "foo=[$$foo]"

    # Now let's try making something that depends on something else. In this case,
    # we're going to create a file called "result.txt" which depends on
    # "source.txt".

    result.txt: source.txt
    @echo "building result.txt from source.txt"
    cp source.txt result.txt

    # When we type `make result.txt`, we get an error!
    # $ make result.txt
    # make: *** No rule to make target `source.txt', needed by `result.txt'. Stop.
    #
    # The problem here is that we've told make to create result.txt from
    # source.txt, but we haven't told it how to get source.txt, and the file is
    # not in our tree right now.
    #
    # Un-comment the next ruleset to fix the problem.
    #
    #source.txt:
    # @echo "building source.txt"
    # echo "this is the source" > source.txt
    #
    # Run `make result.txt` and you'll see it first creates source.txt, and then
    # copies it to result.txt. Try running `make result.txt` again, and you'll see
    # that nothing happens! That's because the dependency, source.txt, hasn't
    # changed, so there's no need to re-build result.txt.
    #
    # Run `touch source.txt`, or edit the file, and you'll see that
    # `make result.txt` re-builds the file.
    #
    #
    # Let's say that we were working on a project with 100 .c files, and each of
    # those .c files we wanted to turn into a corresponding .o file, and then link
    # all the .o files into a binary. (This is effectively the same if you have
    # 100 .styl files to turn into .css files, and then link together into a big
    # single concatenated main.min.css file.)
    #
    # It would be SUPER TEDIOUS to create a rule for each one of those. Luckily,
    # make makes this easy for us. We can create one generic rule that handles
    # any files matching a specific pattern, and declare that we're going to
    # transform it into the corresponding file of a different pattern.
    #
    # Within the ruleset, we can use some special syntax to refer to the input
    # file and the output file. Here are the special variables:
    #
    # $@ The file that is being made right now by this rule (aka the "target")
    # $< The input file (that is, the first prerequisite in the list)
    # $? All the input files that are newer than the target
    # $$ A literal $ character inside of the rules section
    # $* The "stem" part that matched in the rule definition's % bit
    #
    # You can also use the special syntax $(@D) and $(@F) to refer to just the dir
    # and file portions of $@, respectively. $(<D) and $(<F) work the same way
    # on the $< variable.
    #
    # So, our rule for result.txt could've been written like this instead:

    result-using-var.txt: source.txt
    @echo "buildling result-using-var.txt using the $$< and $$@ vars"
    cp $< $@

    # Let's say that we had 100 source files, that we want to convert into 100
    # result files. Rather than list them out one by one in the makefile, we can
    # use a bit of shell scripting to generate them, and save them in a variable.
    #
    # Note that make uses := for assignment instead of =
    # Also, usually you'd use `$(wildcard src/*.txt)` instead, since probably the
    # files would already exist in your project. Since this is a tutorial, though
    # we're going to generate them using make.

    # This will execute the shell and run the program to generate a list of files.
    srcfiles := $(shell echo src/{00..99}.txt)

    # How do we make a text file in src?
    # First the folder must exist, so that's a dependency.
    # We define the filename using a "stem" with the % as a placeholder.
    # What this means is "any file named src/*.txt, but build src first"
    src/%.txt: src
    @# then, we just echo some data into the file
    @# The $* expands to the "stem" bit matched by %
    @# So, we get a bunch of files with numeric names, containing their number
    echo $* > $@

    # To build the directory, we'll just create it using mkdir
    src:
    mkdir -p src

    # But wait, won't this build the directory each time?
    #
    # Nope, because Make is smart. In the first run, it sees that the folder
    # doesn't exist, and makes it. Then, it sees that nothing's changed, so it's
    # skipped.
    #
    # Try running `make src/00.txt` and `make src/01.txt` now.


    # To not have to run make for each file, we define a "phony" target that
    # depends on all of the srcfiles, and has no other rules. It's good practice
    # to define your phony rules in a .PHONY declaration in the file. (See the
    # .PHONY entry at the very bottom of this file.)
    #
    # Running `make source` will make ALL of the files in the src/ dir. Before it
    # can make any of them, it'll first make the src/ dir itself. Then it'll copy
    # the "stem" value (that is, the number in the filename matched by the %) into
    # the file, like the rule says above.
    #
    # Try typing "make source" to make all this happen.
    source: $(srcfiles)

    #
    # So, to make a dest file, let's copy a source file into its destination.
    # Also, it has to create the destination folder first.
    #
    # The destination of any dest/*.txt file is the src/*.txt file with the
    # matching stem. You could just as easily say that %.css depends on %.styl
    dest/%.txt: src/%.txt dest
    cp $< $@

    dest:
    mkdir -p dest

    # So, this is great and all, but we don't want to type `make dest/#.txt`
    # 100 times!
    #
    # Let's create a "phony" target that depends on all of the destination files.
    # We can use the built-in pattern substitution "patsubst" so we don't
    # have to re-build the list. This uses the same "stem"

    destfiles := $(patsubst src/%.txt,dest/%.txt,$(srcfiles))
    destination: $(destfiles)

    # Since "destination" isn't an actual filename, we define that as a .PHONY
    # as well (see below). This way, Make won't bother itself checking to see
    # if the file named "destination" exists if we have something that depends
    # on it later.
    #
    # Let's say that all of these dest files should be gathered up into a proper
    # compiled program. Since this is a tutorial, we'll use the venerable feline
    # compiler called "cat", which is included in every posix system.

    kitty: $(destfiles)
    cat $(destfiles) > kitty

    # Note what's happening here:
    #
    # kitty -> (all of the dest files)
    # Then, each destfile depends on a corresponding srcfile
    #
    # If you `make kitty` again, it'll say "kitty is up to date"
    #
    # NOW TIME FOR MAGIC!
    #
    # Let's update just ONE of the source files, and see what happens
    #
    # Run this: touch src/25.txt; make kitty
    #
    # Note that it is smart enough to re-build JUST the single destfile that
    # corresponds to the 25.txt file, and then concats them all to kitty. It
    # *doesn't* re-generate EVERY source file, and then EVERY dest file, every time


    # It's good practice to have a `test` target, because people will come to your
    # project, and if there's a Makefile, then they'll expect `make test` to do
    # something.
    #
    # We can't test the kitty unless it exists, so we have to depend on that.
    test: kitty
    @echo "miao" && echo "tests all pass!"

    # Last but not least, `make clean` should always remove all of the stuff that
    # your makefile created, so that we can remove bad stuff if anything gets
    # corrupted or otherwise screwed up.
    clean:
    rm -rf *.txt src dest

    .PHONY: source destination clean test