Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prompt for target-specific Makefile variable if undefined?

This is similar to another issue, but I only want make to prompt for a value if I'm running a specific target and a mandatory variable has not been specified.

The current code:

install-crontab: PASSWORD ?= "$(shell read -p "Password: "; echo "$$REPLY")"
install-crontab: $(SCRIPT_PATH)
    @echo "@midnight \"$(SCRIPT_PATH)\" [...] \"$(PASSWORD)\""

This just results in the following output and no prompt:

Password: read: 1: arg count
@midnight [...] ""

The important point here is that I have to ask only when running this target, and only if the variable has not been defined. I can't use a configure script, because obviously I shouldn't store passwords in a config script, and because this target is not part of the standard installation procedure.

like image 971
l0b0 Avatar asked Jan 08 '12 17:01

l0b0


People also ask

How do you check if a variable is defined in Makefile?

Check if variable is defined in a Makefilecheck_defined = \ $(strip $(foreach 1,$1, \ $(call __check_defined,$1,$(strip $(value 2))))) __check_defined = \ $(if $(value $1),, \ $(error Undefined $1$(if $2, ($2)))) install: $(call check_defined, var1) $(call check_defined, var2) # do stuff here..

What is := in Makefile?

Variables defined with ' = ' are recursively expanded variables. Variables defined with ' := ' or ' ::= ' are simply expanded variables; these definitions can contain variable references which will be expanded before the definition is made.


2 Answers

Turns out the problem was that Makefiles don't use Dash / Bash-style quotation, and that Dash's read built-in needs a variable name, unlike Bash. Resulting code:

install-crontab-delicious: $(DELICIOUS_TARGET_PATH)
    @while [ -z "$$DELICIOUS_USER" ]; do \
        read -r -p "Delicious user name: " DELICIOUS_USER;\
    done && \
    while [ -z "$$DELICIOUS_PASSWORD" ]; do \
        read -r -p "Delicious password: " DELICIOUS_PASSWORD; \
    done && \
    while [ -z "$$DELICIOUS_PATH" ]; do \
        read -r -p "Delicious backup path: " DELICIOUS_PATH; \
    done && \
    ( \
        CRONTAB_NOHEADER=Y crontab -l || true; \
        printf '%s' \
            '@midnight ' \
            '"$(DELICIOUS_TARGET_PATH)" ' \
            "\"$$DELICIOUS_USER\" " \
            "\"$$DELICIOUS_PASSWORD\" " \
            "\"$$DELICIOUS_PATH\""; \
        printf '\n') | crontab -

Result:

$ crontab -r; make install-crontab-delicious && crontab -l
Delicious user name: a\b c\d
Delicious password: e f g
Delicious backup path: h\ i
no crontab for <user>
@midnight "/usr/local/bin/export_Delicious" "a\b c\d" "e f g" "h\ i"
$ DELICIOUS_PASSWORD=foo make install-crontab-delicious && crontab -l
Delicious user name: bar
Delicious backup path: baz
@midnight "/usr/local/bin/export_Delicious" "a\b c\d" "e f g" "h\ i"
@midnight "/usr/local/bin/export_Delicious" "bar" "foo" "baz"

This code:

  • treats all input characters as literals, so it works with spaces and backslashes,
  • avoids problems if the user presses Enter without writing anything,
  • uses environment variables if they exist, and
  • works whether crontab is empty or not.
like image 156
l0b0 Avatar answered Nov 15 '22 13:11

l0b0


l0b0's answer helped me with a similar problem where I wanted to exit if the user doesn't input 'y'. I ended up doing this:

@while [ -z "$$CONTINUE" ]; do \
    read -r -p "Type anything but Y or y to exit. [y/N] " CONTINUE; \
done ; \
if [ ! $$CONTINUE == "y" ]; then \
if [ ! $$CONTINUE == "Y" ]; then \
    echo "Exiting." ; exit 1 ; \
fi \
fi

I hope that helps someone. It's hard to find more info about using user input for an if/else in a makefile.

like image 25
thrashr888 Avatar answered Nov 15 '22 12:11

thrashr888