8

I work on a web app whose Makefile contains the following:

dist/index.html: src/templates/index.html
    @bin/insert-scripts $< --output $@

bin/insert-scripts replaces <--scripts--> in the provided file with one of the following:

  • a number of script tags (for jQuery, Underscore, etc.) when $ENV is "development", or
  • a single script tag (pointing to the concatenated, minified file) when $ENV is "production".

The problem is that if one builds dist/index.html in one mode ("development", say), and then builds it again in the other mode without touching the dependency, make will say there's nothing to be done. What I would like to be able to do is to make $ENV a dependency of dist/index.html:

dist/index.html: src/templates/index.html $ENV
    @bin/insert-scripts $< --output $@

This won't work, of course, so I considered having a file named ENV which contains either "development" or "production". This file would become a dependency:

dist/index.html: src/templates/index.html ENV
    @bin/insert-scripts $< --output $@

Rather than setting an environment variable, one would set the content of the ENV file. This seems a little clunky, but at least accurately represents the dependency tree.

What is the best way to handle this situation?

davidchambers
  • 23,918
  • 16
  • 76
  • 105
  • Hmm, thought I had an answer for you but I misread what you're about. I thought the Makefile was changing and then you would just need to add Makefile as a dependency. For what you want you could place a shell command in your Makefile to compare the current content of dist/index.html with what $ENV implies it should be (by grepping for a tag that only exists in one of the two forms, or modify your insert-scripts to set a comment in the file you can test). If $ENV and the file content don't match, touch the dependency. Then, when the rule runs it will see it needs to update. – William Feb 12 '13 at 21:30

3 Answers3

6

If you absolutely have to enforce rebuilding for changed environments, you can always use a tag file for the build environment:

.PHONY: always-rebuild

environment : always-rebuild
   echo $ENV > $@.tmp
   diff --quiet $@ $@.tmp || cp $@.tmp $@
   rm -f $@.tmp

dist/index.html : src/templates/index.html environment

The diffing ensures that environment is always re-built (<= checked), but only touched when the relevant environment variables changed.

thiton
  • 35,651
  • 4
  • 70
  • 100
5

So, you want make to run the scripts in the following two cases:

  • src/templates/index.html changed
  • ENV environment variable changed since last time you generated dist/index.html

The problem with this requirement is that environment variables have no timestamps. Therefore, make cannot know if the target is up-to-date.

Usually the solution in similar situations is to simply have separate targets, e.g. dist-development/index.html and dist-production/index.html. You can even find a way to use symbolic links or something to efficiently point the web app to the correct latest version of index.html. But the alternative of using an ENV file is also a possibility. I would suggest a little refinement to your procedure:

.PHONY: ENV
ifneq "$(ENV)" "$(shell cat ENV)"
dist/index.html: ENV src/templates/index.html
    @bin/insert-scripts $< --output $@
else
dist/index.html: src/templates/index.html
    @bin/insert-scripts $< --output $@
endif

ENV:
    echo $(ENV) >$@

This way, your make will accept the current setting of $ENV and keep it in the file with correct timestamp.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
2

Make always works best when all relevant information is encoded in the filename:

all : dist-${ENV}/index.html

dist-development/index.html : src/templates/index.html
    ENV=development bin/insert-scripts $< --output $@

dist-production/index.html : src/templates/index.html
    ENV=production bin/insert-scripts $< --output $@
thiton
  • 35,651
  • 4
  • 70
  • 100