5

I want to add a Make target that runs a program with envdir.

ENVDIR := $(shell command -v envdir)

serve: | $(ENVDIR)
    $(ENVDIR) /usr/local/myservice/env python -m SimpleHTTPServer 8000

The idea is that if the user doesn't have envdir installed, it will install it for them. I need the pipe character because I don't always want it to run; I just want any version of it to be present. Unfortunately, I can't predict very well where envdir will be installed to.

UNAME := $(shell uname)
$(ENVDIR):
ifeq ($(UNAME), Darwin)
    brew install daemontools
endif
ifeq ($(UNAME), Linux)
    sudo apt-get install daemontools
endif
# Add other operating systems here as appropriate...

The other problem I run into is that if the user doesn't have envdir installed, the variable evaluates to the empty string. So a) My "install envdir" target doesn't actually run, and b) after it runs, $(ENVDIR) is still set to the empty string, so loading it in the serve target doesn't work.

Is there a way to a) get the install target to run, even if ENVDIR is undefined, and b) to redefine the value of the variable at the end of the "install envdir" target run?

Please don't respond with "why are you using envdir" - I run into this problem with a variety of different binaries I depend upon, this just happened to be an easily shareable example.

Kevin Burke
  • 61,194
  • 76
  • 188
  • 305
  • Why don't you use a conditional to distinguish between cases where `envdir` is already installed and cases where it is not? `ifeq ($(ENVDIR),)...else...endif` could probably make it. And at the end of the `envdir` install recipe, you could simply re-invoke make instead of trying to change the value of the `ENVDIR` variable (I know, *recursive make is bad*, but bad things are sometimes extremely useful). – Renaud Pacalet Aug 31 '17 at 11:35

2 Answers2

1

For the "empty" issue you can do this:

ENVDIR := $(or $(shell command -v envdir),envdir)

so that if the shell function expands to the empty string, envdir will be substituted (the or function was added in GNU make 3.81). That will probably be enough to solve all your problems since after the installation the envdir command will now appear on the PATH, so you don't need the full path.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
1

You could do it completely in the shell as so:

ENVDIR := $( command -v envdir 2> /dev/null || ( install envdir > /dev/null && echo newEnvDirPath ) )

This is simplest, but causes envdir to be installed, even if it's not required for the current target. If you want it in a target, you could do it something like:

ENVDIR:=$(cat .ENVDIR 2> /dev/null)
$(type -v $(ENVDIR) > /dev/null || rm .ENVDIR )

.ENVDIR:
    command -v envdir 2> /dev/null > $@ || ( install envdir > /dev/null && echo newEnvDirPath > $@)
    $(eval ENVDIR:=$$(cat $@))

shell: | .ENVDIR

If the file .ENVDIR does not exist, it will run the recipe, which will either write the installed path to .ENVDIR, or it will install and write the new path to .ENVDIR. Either way, when the recipe line finishes, .ENVDIR will contain the executable path. The target then runs $(eval) to set ENVDIR. Notice that calling eval causes the makefile to be reparsed, which is not ideal, but should only happen once on the system. (Also note the double $$ in front of the cat command, which prevents make from expanding this at read time...).

Alternatively, if .ENVDIR does exist, it will read the file to determine which envdir to try to use. A check is done on the next line to ensure that that version still exists on the system, and if not, it deletes the file, forcing it to be recreated.

blackghost
  • 1,730
  • 11
  • 24