GNU make with many target directories

2019-03-21 11:38发布

I have to integrate the generation of many HTML files in an existing Makefile. The problem is that the HTML files need to reside in many different directories. My idea is to write an implicit rule that converts the source file (*.st) to the corresponding html file

%.html: %.st
    $(HPC) -o $@ $<

and a rule that depends on all html files

all: $(html)

If the HTML file is not in the builddir make doesn't find the implicit rule: *** No rule to make target. If I change the implicit rule like so

$(rootdir)/build/doc/2009/06/01/%.html: %.st  
    $(HPC) -o $@ $<

it's found, but then I have to have an implicit rule for nearly every file in the project. According to Implicit Rule Search Algorithm in the GNU make manual, rule search works like this:

  1. Split t into a directory part, called d, and the rest, called n. For example, if t is src/foo.o', then d issrc/' and n is `foo.o'.
  2. Make a list of all the pattern rules one of whose targets matches t or n. If the target pattern contains a slash, it is matched against t; otherwise, against n.

Why is the implicit rule not found, and what would be the most elegant solution, assuming GNU make is used?

Here is a stripped down version of my Makefile:

rootdir  = /home/user/project/doc
HPC      = /usr/local/bin/hpc

html = $(rootdir)/build/doc/2009/06/01/some.html

%.html: %.st
    $(HPC) -o $@ $<

#This works, but requires a rule for every output dir
#$(rootdir)/build/doc/2009/06/01/%.html: %.st  
#   $(HPC) -o $@ $<

.PHONY: all
all: $(html)

4条回答
一纸荒年 Trace。
2楼-- · 2019-03-21 11:48

Like Maria Shalnova I like recursive make (though I disagree with "Recursive Make Considered Harmful"), and in general it's better to make something HERE from a source THERE, not the reverse. But if you must, I suggest a slight improvement: have generateHtml generate only the RULE, not the COMMANDS.

查看更多
唯我独甜
3楼-- · 2019-03-21 11:49

Your active implicit rule makes $(rootdir)/build/doc/2009/06/01/some.html depend on $(rootdir)/build/doc/2009/06/01/some.st. If $(rootdir)/build/doc/2009/06/01/some.st doesn't exist then the rule won't be used/found.

The commented out rule makes $(rootdir)/build/doc/2009/06/01/some.html depend on some.st.

One solution is to make you're source layout match your destination/result layout.

Another option is to create the rules as required with eval. But that will be quite complicated:

define HTML_template
 $(1) : $(basename $(1))
      cp $< $@
endef

$(foreach htmlfile,$(html),$(eval $(call HTML_template,$(htmlfile))))
查看更多
趁早两清
4楼-- · 2019-03-21 11:51

An other possibility is to have the commando make call itself recursively with the argument -C with every output directory. Recursive make is somewhat the standard way to deal with subdirectories, but beware of the implications mentioned in the article "Recursive Make Considered Harmful"

查看更多
倾城 Initia
5楼-- · 2019-03-21 12:04

The best solution I found so far is to generate an implicit rule per target directory via foreach-eval-call, as explained in the GNU make manual. I have no idea how this scales to a few thousand target directories, but we will see...

If you have a better solution, please post it!

Here is the code:

rootdir  = /home/user/project/doc
HPC      = /usr/local/bin/hpc

html = $(rootdir)/build/doc/2009/06/01/some.html \
       $(rootdir)/build/doc/2009/06/02/some.html

targetdirs = $(rootdir)/build/doc/2009/06/01 \
             $(rootdir)/build/doc/2009/06/02

define generateHtml
 $(1)/%.html: %.st
    -mkdir -p $(1)
    $(HPC) -o $$@ $$<
endef   

$(foreach targetdir, $(targetdirs), $(eval $(call generateHtml, $(targetdir))))

.PHONY: all
all: $(html)
查看更多
登录 后发表回答