5 ms·
Two things I try to get people to do with Makefiles: 1. Use the := syntax := means that a variable is immediately expanded; = means it's delayed until the var
by jgrahamc 9y ago
Two things I try to get people to do with Makefiles:
1. Use the := syntax
:= means that a variable is immediately expanded; = means it's delayed until the variable is used which on a large Makefile has a speed penalty. If you know that a variable is fully defined (i.e. all the $(...) references in its value are fixed) when the variable is being defined then use :=. That's the case here where the Makefile starts:
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
BINARY_NAME=mybinary
BINARY_UNIX=$(BINARY_NAME)_unix
which can be written
GOCMD := go
GOBUILD := $(GOCMD) build
GOCLEAN := $(GOCMD) clean
GOTEST := $(GOCMD) test
GOGET := $(GOCMD) get
BINARY_NAME := mybinary
BINARY_UNIX := $(BINARY_NAME)_unix
2. If you have a one line recipe then use the target: preq ; command syntax to avoid tabs. For example,
build:
$(GOBUILD) -o $(BINARY_NAME) -v
is the same as
build: ; $(GOBUILD) -o $(BINARY_NAME) -v
- skybrian 9y agoThat's good advice, but in this case I'm wondering, why use variables at all? The makefile is only being used as a collection of aliases. They could all be inlined without losing anything.
- jgrahamc 9y agoI agree. This Makefile would be clearer with something like: BINARY_NAME := mybinary BINARY_UNIX := $(BINARY_NAME)_unix .PHONY: all test build clean run deps all: test build build: ; go build -o $(BINARY_NAME) -v test: ; go test -v ./... clean: ; go clean && rm -f $(BINARY_NAME) $(BINARY_UNIX) run: ; go run $(BINARY_NAME) -v ./... deps: DEPS := $(addprefix github.com/markbates/,goth pop) deps: ; go get $(DEPS) build-linux: ; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $(BINARY_UNIX) -v docker-build: ; docker run --rm -it -v "$(GOPATH)":/go -w /go/src/bitbucket.org/rsohlich/makepost golang:latest go build -o "$(BINARY_UNIX)" -v Caveat. I haven't tested that, but something close to that would be much clearer.
- titanomachy 9y agoShould `all` also be in `.PHONY` or is it a special case?
- jgrahamc 9y agoI just forgot it.
- jokr004 9y agoWell, the idea is that maybe the command 'go' is different on different build systems/targets and if you need to change it to something else (go-x86, or something like that) then it's going to be more difficult the way you wrote things.
- skybrian 9y agoSure, that's what variables are for. But in a small makefile, it's a simple search-and-replace to move it out to a variable. Adding indirection slightly degrades readability, so better to wait until it's first needed.
- jokr004 9y agoVery true.
- vog 9y agoIndeed, those variables are very handy for cross compiling. If you don't do anything else, at least define and use a $(CROSS) prefix. (Or, don't and wait for some cross compiling distro to approach you and to offer a patch that introduces such a variable.)
- mikegerwitz 9y agoHaving a bunch of variables is more useful once you get into Auto{make,conf}, where programs like the compiler can be auto-discovered and can be overridden by the user at configure-time.
- Twirrim 9y ago> = means it's delayed until the variable is used which on a large Makefile has a speed penalty Do you mean it's expanded every time it is used, vs once? If not, it's not clear to me why it would be slower.
- astrodust 9y agoIt's deferred interpolation, which means every time.
- Twirrim 9y agoOk, so very painful at scale.
- coldtea 9y agoShouldn't it be insignificant, even at scale? Heck, JS spawns functions at scale for everything, and we're worrying about mere interpolation?
- tedunangst 9y agoThe make variable interpolator may not be as optimized as the javascript function spawner.
- TheSoftwareGuy 9y agoI wouldn't be so sure about that. make has been around for several decades now, and it affects the build times of a huge number of projects. Most common versions of make have been very tightly optimized, in ways that you might not expect.
- coldtea 9y agoYeah, but it still shouldn't matter, because there's not much to optimize: string substitution is inherently fast. But it's what your sibling comment said that I missed: it might not just be string interpolation, it could bound to a shell command's result etc -- which would then be executed at every occurrence.
- wruza 9y agoWhile it is not so important in simple cases, '=' allows to not depend on the order of definitions, so you can group variables in logical order, not as statements. You can look at := as variable definition and = as semi-function definition. Penalty of delayed-but-many variable expansion that doesn't contain shell code is questionable. Also, with := half of them may not be used by 'maked' rule at all, actually losing performance. There is also ?= which only sets a variable that was not set before or via command line or environment (make inherits current environment variables iirc).
- dom0 9y ago> and = as semi-function definition. Variable binding.
- klodolph 9y agoWell, = doesn't really behave like traditional variable binding, since you can do stuff like: build = cc -c -o $@ $< file1.o: file1.c $(build) file2.o: file2.c $(build) (This is meant to illustrate the difference, this isn't a good makefile.) To reinforce the notion that variables are really functions, you can even supply arguments when you call them: # reverse is a function reverse = $(2) $(1) # eventually expands to 2 1 a = $(call reverse,1,2)
- KSteffensen 9y agoPlease don't rely on environment variables. It plays havoc with reproducibility.
- wruza 9y agoI'm usually adding 'vars' target to my makefiles that simply echoes them to see if anything is wrong or simply to see defaults. There is also command-line option to do that (and of course -n), but it prints too much to comprehend.