natevw
natevw

Reputation: 17932

Makefile: how to declare a dependency for all targets that match a pattern?

Certain files in my target have dependencies on other targets. I can ensure a proper build by adding explicit rules like this:

static/app.mjs: js2/.legacy_app.built.mjs
static/admin_unit.mjs: js2/.legacy_admin.built.mjs
static/admin_source.mjs: js2/.legacy_admin.built.mjs
static/admin_module.mjs: js2/.legacy_admin.built.mjs

But this means changing my Makefile every time I add a new "admin_X" source to my project. What I would like to do is have a catch-all pattern rule like:

static/app.mjs: js2/.legacy_app.built.mjs
static/admin_%.mjs: js2/.legacy_admin.built.mjs

But this does not work, as explained by https://stackoverflow.com/a/3734705/179583:

Pattern rules with no recipes at all are documented as meaning something quite different [… :] they cancel any pre-existing implicit rule

Is there a clean way to specify that "any target matching a certain pattern" depends on some particular other target?

Upvotes: 2

Views: 1810

Answers (3)

natevw
natevw

Reputation: 17932

I found another trick to do this, under the special case that all relevant files are built via their own particular recipe. I.e. this will not work in the general case, unless you have split out or can duplicate a separate recipe for the targets in question.

Granting that, it's a simple matter of tying in the common dependency via a phony target. This ADMIN target depends on the shared intermediate file, and then in turn is specified as a prequisite of a pattern-matching recipe:

.PHONY: ADMIN
ADMIN: js2/.legacy_admin.built.mjs

static/admin_%.mjs static/admin_%.mjs.map: js2/admin_%.build.mjs ADMIN FORCE
    node_modules/.bin/rollup --config rollup.config.js $< --format esm --sourcemap -o $@

# …also contains recipe to build "js2/.legacy_admin.built.mjs" itself

Now before any static/admin_%.mjs file is built, make ensures that the common js2/.legacy_admin.built.mjs helper file has been created.

(See when multiple pattern rules match a target for some background on how various versions of Make pick which recipe to use.)

Upvotes: 0

Mike Kinghan
Mike Kinghan

Reputation: 61610

As you know, a pattern rule like:

static/admin_%.mjs: js2/.legacy_admin.built.mjs
    recipe...

does not specify any targets, just a template for discovering the prerequisites of targets that you specify otherwise, with a recipe for making those targets from the prerequisites.

So there has to be something else in your makefile that determines what the targets are. Let's suppose it is just a list as in:

$ cat Makefile
MJS_STEMS := app admin_unit admin_source admin_module
MJS_FILES := $(addprefix static/,$(MJS_STEMS:%=%.mjs))

.PHONY: all clean
all: $(MJS_FILES)

$(MJS_FILES): js2/.legacy_admin.built.mjs

static/%.mjs: | static
    @echo $< > $@
    @echo "$@ depends on $<"

js2/.legacy_admin.built.mjs: | js2
    touch $@

static js2:
    mkdir -p $@

clean:
    $(RM) $(MJS_FILES) js2/.legacy_admin.built.mjs

Here,

$(MJS_FILES): js2/.legacy_admin.built.mjs

says that each of $(MJS_FILES) depends on js2/.legacy_admin.built.mjs. That's the most concise way to do it without a pattern rule. The make runs like:

$ make
mkdir -p static
mkdir -p js2
touch js2/.legacy_admin.built.mjs
static/app.mjs depends on js2/.legacy_admin.built.mjs
static/admin_unit.mjs depends on js2/.legacy_admin.built.mjs
static/admin_source.mjs depends on js2/.legacy_admin.built.mjs
static/admin_module.mjs depends on js2/.legacy_admin.built.mjs

If you want you can move the maintenance of the MJS_STEMS list out of the makefile into another file:

$ cat ./mjs_stems
app
admin_unit
admin_source
admin_module

$ cat Makefile
MJS_STEMS := $(shell cat ./mjs_stems)
MJS_FILES := $(addprefix static/,$(MJS_STEMS:%=%.mjs))

.PHONY: all clean
all: $(MJS_FILES)

$(MJS_FILES): js2/.legacy_admin.built.mjs

static/%.mjs: | static
    @echo $< > $@
    @echo "$@ depends on $<"

js2/.legacy_admin.built.mjs: | js2
    touch $@

static js2:
    mkdir -p $@

clean:
    $(RM) $(MJS_FILES) js2/.legacy_admin.built.mjs


$ make clean
rm -f static/app.mjs static/admin_unit.mjs static/admin_source.mjs static/admin_module.mjs js2/.legacy_admin.built.mjs

$ echo "admin_foobar" >> mjs_stems
$ make
touch js2/.legacy_admin.built.mjs
static/app.mjs depends on js2/.legacy_admin.built.mjs
static/admin_unit.mjs depends on js2/.legacy_admin.built.mjs
static/admin_source.mjs depends on js2/.legacy_admin.built.mjs
static/admin_module.mjs depends on js2/.legacy_admin.built.mjs
static/admin_foobar.mjs depends on js2/.legacy_admin.built.mjs

Or you can just get the MJS_STEMS list from the environment:

$ cat Makefile
MJS_STEMS := $(strip $(MJS_STEMS))
MJS_FILES := $(addprefix static/,$(MJS_STEMS:%=%.mjs))

.PHONY: all clean
all: $(MJS_FILES)

$(MJS_FILES): js2/.legacy_admin.built.mjs

static/%.mjs: | static
    @echo $< > $@
    @echo "$@ depends on $<"

js2/.legacy_admin.built.mjs: | js2
    touch $@

static js2:
    mkdir -p $@

clean:
    $(RM) $(MJS_FILES) js2/.legacy_admin.built.mjs


$ make clean
rm -f static/app.mjs static/admin_unit.mjs static/admin_source.mjs static/admin_module.mjs static/admin_foobar.mjs js2/.legacy_admin.built.mjs

$ export MJS_STEMS=$(cat ./mjs_stems)
$ make
touch js2/.legacy_admin.built.mjs
static/app.mjs depends on js2/.legacy_admin.built.mjs
static/admin_unit.mjs depends on js2/.legacy_admin.built.mjs
static/admin_source.mjs depends on js2/.legacy_admin.built.mjs
static/admin_module.mjs depends on js2/.legacy_admin.built.mjs
static/admin_foobar.mjs depends on js2/.legacy_admin.built.mjs

$ make clean
rm -f static/app.mjs static/admin_unit.mjs static/admin_source.mjs static/admin_module.mjs static/admin_foobar.mjs js2/.legacy_admin.built.mjs

$ export MJS_STEMS="aa bb cc"
$ make
touch js2/.legacy_admin.built.mjs
static/aa.mjs depends on js2/.legacy_admin.built.mjs
static/bb.mjs depends on js2/.legacy_admin.built.mjs
static/cc.mjs depends on js2/.legacy_admin.built.mjs

$ make clean
rm -f static/aa.mjs static/bb.mjs static/cc.mjs js2/.legacy_admin.built.mjs

But one way or another, somewhere, you have to specify the list of targets, and to add new targets you have to update something.

Upvotes: 1

Matt
Matt

Reputation: 15196

If all those files already exist in the filesystem (i.e. not supposed to be built by make from scratch), you can use $(wildcard ...):

$(wildcard static/admin_*.mjs): js2/.legacy_admin.built.mjs

Upvotes: 1

Related Questions