A Makefile to make some Cheese Excuse the pun in the title, the project this is for is called "Cheddar" (a type of cheese).

Github



I have recently been writing a language, in which I was using grunt. Grunt was very slow for me, and had a lot more problems for me, so I switched my tasks over to makefile. Most of my makefile knowledge is based off examples, googling, and brief tutorials, which is why I want to ask here on how it could be improved.

PREFIX=./node_modules/.bin
## Compiler
# The compiler to use for the ES7 -&gt; ES5 transformation
JC=$(PREFIX)/babel
# Location of files
SRC=src/
OUT=dist/
# The flags to pass to the compiler
JCFLAGS=$(SRC) -d $(OUT)
# The binary files to copy
BIN=cli/cheddar
EXE=cheddar
BIN_MAKE=$(foreach BIN_FILE,$(BIN),chmod 755 $(SRC)$(BIN_FILE) &amp;&amp; cp $(SRC)$(BIN_FILE) $(OUT)$(BIN_FILE)${\n});\
         ln -s $(OUT)cli/$(EXE) ./$(EXE)

## Tests
TESTRUNNER=$(PREFIX)/babel-node
COVERAGE=$(PREFIX)/babel-istanbul
TEST=$(PREFIX)/_mocha
TFLAGS=cover $(TEST)

## Rules
all: default

# The default task
# The **production** build
default: $(JC) $(SRC)
    $(JC) $(JCFLAGS) --minified
    $(BIN_MAKE)
# Development build task
# This builds and includes source maps
build: $(JC) $(SRC)
    $(JC) $(JCFLAGS) --sourceMaps="inline"
    $(BIN_MAKE)

# Runs install with default method
install: ./bin/install
    ./bin/install --method=path

# Performs testing, including coverage
# At the moment uses mocha for testing 
# and babel-istanbul for coverage
test: $(TESTRUNNER) $(COVERAGE) $(TEST)
    $(TESTRUNNER) $(COVERAGE) $(TFLAGS)
clean:
    rm -rf ./dist/
 .PHONY: test


Essentially what this does is it defined a couple of tasks:


default: This is the default task. It runs babel, a compiler I need to compile the code from the src/ directory to the output dist/ directory.  It passes the --minified flag because this is a production build so we don't want big unminified code.
Following is BIN_MAKE. This runs code to chmod 755 a list of binary files stored in the BIN variable. It then moves these files from the source dir src/ to the destination/output dir dist/.

Lastly, it symlinks the executable to start the program from deep within the dist/ directory to the root for easy-access.
build:: This is a development build. It compiled the same as above, but instead of --minify passes a --sourceMap arg to create source maps
install: This runs an install script to install the application into the user's computer. The script's contents is located in an external file.
test: This runs a file to simply test the application. If you know Mocha or Istanbul, this just runs that. _mocha and babel-istanbul are in variables as they are located in ./node_modules/.bin/.
clean: This removes the dist/ dir which is the location of the output of the build.
In makefiles you can have spacing around equals signs, making the code slightly easier to read. For example PREFIX = ./node_modules/.bin.
UPPER_CASE variables are generally used for things that the end user might want to change when running, while lower_case is for internal use variables. The user is presumably not supposed to change for example the source directory, so SRC should really be src.
Single-use internal variables like BIN make the code harder to read for no tangible benefit. Introduce a variable when you need to reuse the value.
Any target which has a name that doesn't correspond to an actual file created by that target should IMO be marked as .PHONY (because it is).
Conversely, targets which do generate result files from their dependencies should be named for the files they generate, so that the Magic of Make™ can kick in and skip targets when they are up to date.
BIN_MAKE would be much easier to understand as either commands in a target or as a separate script.
The default target of a Makefile is the first one, by definition. That makes it redundant to have two phony targets (all and default) to do the default thing.
Some more space between the targets would be nice.
The comments are a bit verbose. Most of the time I find it's possible to make the code understandable enough (especially by naming things carefully) that comments become redundant.
There should be an end user variable for each command you run that's assumed to be on the PATH, for example CP = /bin/cp. This may be considered overkill, especially for common tools, but it allows people who have tools off the PATH (for example after building it themselves) to run your Makefile without modifying it. A common use case (just like with RVM or Virtualenv) is if you want to test your Makefile with a tool version not installed on your base system.


RM is an implicit variable at least in GNU Make for just this purpose.