1
votes

I'm trying to use a codegen tool for Go to automatically generate some code based on the contents of other go files. The codegen tool will get standard arguments which can be deduced from the name of the file its generating and the name of the file that it's parsing. If I were doing it all manually, it would look like:

foo-tool -name FooInterface -file foo/api.go
foo-tool -name BarInterface -file foo/api.go
foo-tool -name BingInterface -file foo/bing.go
foo-tool -name BazInterface -file foo/baz.go

But I don't want to do it manually, I want to use Make! So I tried to accomplish the same thing with a Makefile and a pattern rule.

foo_FooInterface.go : foo/api.go    
foo_BarInterface.go : foo/api.go
foo_BingInterface.go : foo/bing.go
foo_BazInterface.go : foo/baz.go

foo_%.go : %.go
    $(eval foo_name=$(subst mock_,,$(subst .go,,$(@F))))
    build-foo -name $(foo_name) -file $<

In my mind, the first 3 rules would set up the dependency graph, and the pattern rule would tell Make what to do with the dependencies. But when I try running make foo_BarInterface.go, I get make: Nothing to be done for foo_BarInterface.go. I understand why this happens, because Make is expecting to match foo_FooInterface.go with FooInterface.go, but I don't want to restructure my project's files like that.

Is this possible, or do I need to do something like:

foo_FooInterface.go : foo/api.go
    build-foo -name FooInterface -file foo/api.go

foo_BarInterface.go : foo/api.go
    build-foo -name BarInterface -file foo/api.go

foo_BingInterface.go : foo/bing.go
    build-foo -name BingInterface -file foo/bing.go

foo_BazInterface.go : foo/baz.go
    build-foo -name BingInterface -file foo/baz.go

Which I really don't want to do, because new Interfaces will be added as the codebase grows, and I don't want to require people to type all of that every time.

Edit: I wouldn't mind specifying the rule manually every time, but I need a rule that collects all the generated files together, and I don't want to specify every foo_*.go in that one. Is there a way to say "This rule depends on all rules (not files) matching a pattern?" I was able to do

foo_files := $(shell grep 'foo_\w\+.go' Makefile | cut -d : -f1)

But this seems bad to me.

3
What is the name of the file you're trying to build? Is it foo_FooInterface.go, or foo_FooInterface, or FooInterface, or what?Beta
The file that will be built will have the name foo_FooInterface.go. The build-foo tool creates files with that name based on the -name FooInterface argument. I guess I should have been more specific about that.Lily Mara
From your comments to the given aswers, it seems to me that you provided an incomplete example: do I understand correctly that in the real project each of your foo_*.go files depends on a different set of prerequisite files, not just foo/api.go ? If this is the case, can you show us an example closer to what you really need?Dario
Apologies, I was trying to provide as simple of an example as possible, and I guess I went a little too simple. Edited for a more complete example.Lily Mara

3 Answers

1
votes

Nothing easier:

foo_%.go : foo/api.go
    foo-tool -name $* -file $<

And if you want a rule that gathers them all together:

INTERFACES := Foo Bar Bing
FILES := $(patsubst %, foo_%Interface.go, $(INTERFACES))

all: $(FILES)
    @echo do something with $^
1
votes

You can write a single common recipe for the three rules:

foo_FooInterface.go foo_BarInterface.go foo_BingInterface.go: foo/api.go
    foo-tool -name $(@:foo_%.go=%) -file $<

The common recipe is executed separately for each of the individual targets individually, i.e.: once for foo_FooInterface.go, once for foo_BarInterface.go and once for foo_BingInterface.go, not only once for all of them together.

1
votes

If each of your target files has different prerequisites, you cannot avoid to specify them individually. You can do that with empty rules, as you did:

foo_FooInterface.go : foo/api.go    
foo_BarInterface.go : foo/api.go
foo_BingInterface.go : foo/bing.go
foo_BazInterface.go : foo/baz.go

However, with this style of makefile, the rules to build your targets cannot have prerequisites: if they do, the prerequisites specified in the empty rules are ignored. So what you need is the simple rule

foo_%.go:
    foo-tool -name $* -file $<

(note that $^ is expanded as the list of all prerequisites, $< only the first one. In your example there is no difference, but there might be.)

I am not aware of any way to specify “all rules in a makefile matching a pattern”. But it costs little to “register” all desired names in a variable, as @Beta has suggested. To put it all together:

# Register all your components here
INTERFACES := Foo Bar Bing Baz

# Make ’em all
.PHONY : all
all: $(patsubst %, foo_%Interface.go, $(INTERFACES))

# Specify individual dependencies with empty rules
foo_FooInterface.go : foo/api.go    
foo_BarInterface.go : foo/api.go
foo_BingInterface.go : foo/bing.go
foo_BazInterface.go : foo/baz.go

# This is your catch-(almost-)all rule
foo_%.go:
    foo-tool -name $* -file $<

So, every new target requires only registering its name in INTERFACES and adding a line with its dependency. In my opinion, trying to add the dependency line only and extrapolate the existence of a new target from it (as you did by grepping the makefile for foo_\w\+.go), decreases the overall readability with no real benefit.