Reputation: 331
I want to write a Makefile to install go program and other assets, so I want a installdir
. I want to check if GOBIN, GOPATH is set, if not, want user to enter a installdir.
I wrote the Makefile as following, but the makefile variable installdir is empty. echo
output nothing.
installdir:=$(shell echo $(GOPATH) | cut -d':' -f1)
all: *.go
@GO111MODULE=on GOPATH=$(GOPATH) go build -o trpc main.go
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
Please help!
Upvotes: 0
Views: 3569
Reputation: 100816
This code:
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
simply cannot work and represents a fundamental misunderstanding of how make operates.
Make works in two distinct stages: first, it reads and parses all the makefiles, included makefiles, etc. Second, it determines which targets are out of date and runs the recipes to update those targets. Recipes will start a shell and pass the recipe text to that shell. When the shell exits make determines whether it worked or not by looking at the exit code. All make variables and functions in the entire recipe are expanded first, the the shell is invoked on the results. Further, every logical line in the recipe is started in a different shell.
So in your makefile, the ifeq
options (which are makefile constructs) are parsed during the first stage, as the makefile is read in. The recipe lines are not run until the second stage, so changes to the installdir
variable in a recipe cannot impact the ifeq
lines. Further, changes to installdir
in a recipe cannot even be seen by make because they happen in a shell, then the shell exits and those changes are lost.
You'll have to write this entire thing in shell syntax and put all of it into a recipe, something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || read -s -p "Please input installdir: " installdir; \
echo $$installdir
(untested). You have to use shell constructs, not make constructs. You should virtually never use the $(shell ...)
make function inside a recipe: a recipe is already running in a shell. And you have to use backslash/newline pairs to ensure make considers the entire recipe one logical line, else variables set on one line will not be set on the next line.
Finally, I should point out that this (reading input during make) is just generally a bad idea. For example, if you run make install
with the -j
option, only one recipe can have control of stdin and make will choose more-or-less randomly which it is.
Generally instead you want to have the user pass the value on the command line, with something like:
$ make installdir=my/dir
so your check in the makefile should instead be something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || { echo "Please add installdir=... on the command line"; exit 1; }; \
echo $$installdir
Upvotes: 2