0

I have a bunch of targets that are built with the same type of make rule:

   env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/foo foo/*.go
   env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/bar bar/*.go
   env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/*.go
   env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/world world/*.go

and I was trying to write one generic rule, based on

Here is what I come up so far (but not working):

DIRS =$(filter %/, $(wildcard */))

build: $(DIRS)
    export GO111MODULE=on

bin/%: %/$(wildcard *.go)
    env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/$@ $@/*.go
xpt
  • 20,363
  • 37
  • 127
  • 216
  • You've been active on this site far too long to describe a problem as *"not working".* Does your makefile construct `DIRS` properly? Do you want it to be a list of all directories containing files with names ending in `.go`? – Beta Oct 30 '22 at 03:21
  • That's right @Beta, I want DIRS to be a list of all go source directories (that containing files with names ending in .go), but I don't know how to validate if makefile construct DIRS properly. By _"not working"_ I meant not working -- removing a file in bin and invoke make again but the file is ***not*** built. – xpt Oct 30 '22 at 04:10
  • "Not working" could mean 10,000 different things. You should state _specifically_ what you mean. Clearly describing the problem is the most fundamental requirement for asking an answerable question. – MadScientist Oct 30 '22 at 12:45

2 Answers2

1

First we use wildcard to find all files whose names end in .go:

FILES := $(wildcard */*.go)

$(info Files are $(FILES)) # displays the contents of FILES, for debugging

Then we remove the file names, strip the trailing slashes and prepend bin/ by means of patsubst:

TARGS := $(patsubst %/, bin/%,$(dir $(FILES)))

We write a PHONY all rule that requires all of these targets:

.PHONY: all
all: $(TARGS)

and a pattern rule to build them, using automatic variables:

bin/%:
    env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o $@ $*/*.go
Beta
  • 96,650
  • 16
  • 149
  • 150
  • 1
    You probably want to add a `$(sort ...)` to the setting of `TARGS` to avoid duplicates when directories contain multiple `.go` files. It doesn't really hurt to list the same prerequisite multiple times to `all` but if you used this `TARGS` in other places it might matter. – MadScientist Oct 30 '22 at 12:47
  • @MadScientist: I considered that, but didn't want to make the solution too complicated. – Beta Oct 30 '22 at 16:04
1

Beta's answer gives you a solution to your problem but doesn't explain why your solution can't work:

DIRS =$(filter %/, $(wildcard */))

I'm not sure what the filter is for here. It doesn't seem to do anything. This will evaluate to a list of all the subdirectories, not just the subdirectories containing .go files, on most systems. However some systems don't list only directories if you give just /. A more reliable way to list all subdirectories would be:

DIRS := $(patsubst %/.,%,$(wildcard */.))

Note also I used := so this is evaluated only once per make invocation, instead of every time the DIRS variable is evaluated.

build: $(DIRS)
        export GO111MODULE=on

This declares a target build which depends on all DIRS, but the recipe here is useless. It starts a shell, runs that export in the shell, then the shell exits and the export is gone again. It's not possible for a program (any program, not just make) to modify the environment of its parent process.

bin/%: %/$(wildcard *.go)
        env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/$@ $@/*.go

First, the $(wildcard *.go) is expanded when the makefile is parsed, like all targets and prerequisites. So this expands to the list of all .go files in the current directory. I assume there are none and so this expands to nothing. If there were some it would be even worse; suppose you had foo.go and bar.go in the current directory; then this would expand to:

bin/%: %/foo.go bar.go

which is clearly wrong.

Second, you are using $@ in the recipe but $@ expands to the entire target, which is bin/foo for whatever directory foo you have. So bin/$@ expands to bin/bin/foo which is not what you want.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • Thanks for the detailed explanation, exactly what I need to understand where my mistakes are. – xpt Oct 30 '22 at 15:19