Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent running same make command twice?

I've got these two make targets:

NPM_OUT = node_modules/npm.sfv
NPM_BIN = $(shell command -v npm || command -v /usr/bin/npm || echo "npm")
HASH_CMD = $(shell command -v md5 || command -v md5sum)

$(NPM_OUT): npm-shrinkwrap.json
    $(NPM_BIN) install --loglevel=error
    @$(HASH_CMD) npm-shrinkwrap.json > $(NPM_OUT)

npm-shrinkwrap.json:
    $(NPM_BIN) install --loglevel=error
    $(NPM_BIN) prune
    $(NPM_BIN) dedupe
    $(NPM_BIN) shrinkwrap --dev

NPM_OUT is basically a bogus file I use just to determine if npm install has been ran yet. I don't know how to do this without a bogus file because that one command generates many output files, not just one object file like you'd see in c/c++.

The problem I'm having is that if npm-shrinkwrap.json doesn't exist then npm install --loglevel=error gets ran twice.

As you can see, it exists in both targets. If npm-shrinkwrap.json doesn't exist, then I need to run npm install before I can create the shrinkwrap file. But if I do that, then I don't need to run it again for $(NPM_OUT). The reason it's in $(NPM_OUT) is because I need to run it every time npm-shrinkwrap.json changes.

I thought maybe I could create a 3rd target for npm install which the other 2 targets could depend on, but unless I specify a dependency file for that too, then it will always run.

How can I handle this?

like image 441
mpen Avatar asked Mar 23 '26 19:03

mpen


2 Answers

Say the npm install is up-to-date is the state of affairs you want to proxy with the NPM_OUT.

You want the npm install is up-to-date to be dependent on npm-shrinkwrap.json, if and only if npm-shrinkwrap.json exists. If you manually edit npm-shrinkwrap.json, for instance, then that means the npm install is no longer up to up-to-date and must be made so, using npm-shrinkwrap.json.

But if npm-shrinkwrap.json doesn't exist then in fact the dependency is the other way round, because you need to make the npm install up-to-date in order to make npm-shrinkwrap.json, ab initio.

So you have different dependencies depending on whether or not npm-shrinkwrap.json exists:

NPM_BIRTH_CERT = node_modules/.npm_installed.timestamp
NPM_BIN = $(shell command -v npm || command -v /usr/bin/npm || echo "npm")
NPM_SHRINKWRAP := $(wildcard npm-shrinkwrap.json)

.PHONY: all clean really-clean

all: npm-shrinkwrap.json $(NPM_BIRTH_CERT)

$(NPM_BIRTH_CERT): $(NPM_SHRINKWRAP)
    $(NPM_BIN) install --loglevel=error
    touch $@

ifndef NPM_SHRINKWRAP
npm-shrinkwrap.json: $(NPM_BIRTH_CERT)
    $(NPM_BIN) prune
    $(NPM_BIN) dedupe
    $(NPM_BIN) shrinkwrap --dev
    touch $<
endif

clean:
    rm -fr node_modules

really-clean: clean
    rm -f npm-shrinkwrap.json

Here the rule:

$(NPM_BIRTH_CERT): $(NPM_SHRINKWRAP)

is:

$(NPM_BIRTH_CERT): npm-shrinkwrap.json

if npm-shrinkwrap.json exists and otherwise just:

$(NPM_BIRTH_CERT):

And whenever npm-shrinkwrap.json exists it's not a target at all.

I don't see the need make $(NPM_BIRTH_CERT) resolve to an MD5 hash of the npm-shrinkwrap.json, rather than just a file that bears the timestamp of npm installs last completion or npm-shrinkwrap.jsons last generation from an npm install, whichever is latest.

like image 51
Mike Kinghan Avatar answered Mar 25 '26 16:03

Mike Kinghan


Using the ideas from Mike's answer, I think I can get this to behave the way I want:

NPM_SHRINKWRAP := $(wildcard npm-shrinkwrap.json)

ifdef NPM_SHRINKWRAP
$(NPM_OUT): npm-shrinkwrap.json
    $(NPM_BIN) install --loglevel=error
    touch $(NPM_OUT)
else
$(NPM_OUT): npm-shrinkwrap.json
endif

npm-shrinkwrap.json: package.json
    $(NPM_BIN) install --loglevel=error --no-shrinkwrap
    $(NPM_BIN) prune
    $(NPM_BIN) dedupe
    $(NPM_BIN) shrinkwrap --dev
    touch $(NPM_OUT)

Basically, if the shrinkwrap file exists, then make $(NPM_OUT) will run npm install iff the shrinkwrap file has been updated.

If the shrinkwrap file doesn't exist, and since npm-shrinkwrap.json is still a dependency, the npm-shrinkwrap.json target will do the installation instead.

However, I introduced a new problem here: if npm-shrinkwrap.json does exist and package.json (wasn't in original question) has been updated, then the double-install will happen again. I don't know if that can be fixed, or if it's even worth fixing.

like image 21
mpen Avatar answered Mar 25 '26 17:03

mpen