Makefile Automatic Target Generation - macos

I have the following directory tree
moving_files/
Makefile
source/
a
b
c
target/
With my Makefile I want to cp every file in source/ to target/.
The catch: I want to be able to move other files to source/ afterwards without having to edit the Makefile. For this purpose I have written this:
FILES = $(filter-out Makefile, $(wildcard source/*) )
all: $(subst source,target,$(FILES))
$(subst source,target,$(FILES)): $(FILES)
cat $< >| $#
And it works fine.
However, when I execute touch source/d afterwards and make once again, in addition to d, a, b and c get cated, as well. What do I have to do in order to change this behavior.

$(subst source,target,$(FILES)): $(FILES)
expands to
target/a target/b target/c: source/a source/b source/c
which means that each single target depends on all files in source, probably not what you intended. Either a static or an implicit rule can fix this, static rules are generally better as they are more specific:
$(subst source,target,$(FILES)): target/%: source/%

Related

patsubst with multiple wildcards

I have a directory structure like
packages/
foo/
lib/
a.js
b.js
bar/
lib/
c.js
d.js
and I'm trying to write a simple Makefile to run the source files through a compiler and output the result of compiling packages/foo/lib/a.js to packages/foo/build/a.js. In the past, I've done something like
JS = $(shell find packages/foo/lib -name "*.js")
BUILD = $(patsubst packages/foo/lib/%.js, packages/foo/build/%.js, $(JS))
.PHONY: all
all: $(BUILD)
packages/foo/lib/%.js: packages/foo/build/%.js
# Compile $^ and output to $#
which works great. However, I'm now doing
JS = $(shell find packages/*/lib -name "*.js")
BUILD = $(patsubst ...)
The problem here is that patsubst doesn't seem to like multiple % wildcards (ie packages/%/lib/%.js). However, if I use packages/%.js, then I can't know which directory to use in the substitution. I'm convinced there's a very simple way to do this that I can't find in the make docs.
To define BUILD, you could use the stupid but simple solution:
BUILD = $(subst /lib/,/build/,$(JS))
as long as you aren't worried about additional "lib" and "build" pathname components in your source.
However, this leaves you with the problem of how to define the actual Makefile rules, unless you want to manually specify a rule for each directory foo, bar, etc.
Instead, maybe try something like this (remember to replace spaces with tabs again where appropriate, if you cut and paste):
PACKAGES = $(wildcard packages/*)
ALL :=
define add_package
JS := $$(shell find $(1)/lib -name "*.js")
BUILD := $$(patsubst $(1)/lib/%.js, $(1)/build/%.js, $$(JS))
$(1)/build/%.js:: $(1)/lib/%.js
echo COMPILE $$^ '->' $$#
cp $$^ $$# # super fast compiler!
ALL += $$(BUILD)
endef
$(foreach p, $(PACKAGES), \
$(eval $(call add_package,$(p))))
all: $(ALL)
This evaluates a template for each package directory, so you can put anything in there, as long as you get the fiddly doubling of dollar signs correct.

How to use make to build a static website into a dist directory

I have a website in an src directory that is several levels deep, containing html, markdown, less, coffee, png, jpg and other assets. I'd like to build my website into a dist directory using make. With build, I mean
converting markdown files to html
compiling coffee to js
compiling less to css
minifying html files
minifying js files (that where not compiled from coffee)
minifying css files (that where not compiled from less or sass)
preparing images (logo.png becomes logo#1x.png logo#2x.png logo#3x.png)
I have the following file. The cp statements will be replaced with the respective tools to do the transformation.
sources = $(shell find src -type f)
t1 := $(sources:.md=.html)
t2 := $(t1:.less=.css)
targets := $(t2:.coffee=.js)
targetdirs = $(dir $(targets))
all: $(targets)
%.html: %.md
cp $< $#
%.css: %.less
cp $< $#
%.js: %.coffee
cp $< $#
This creates outputs side by side. So src/index.md becomes src/index.html, src/assets/stylesheets/app.less becomes src/assets/stylesheets/app.css and src/assets/scripts/app.coffee becomes src/assets/scripts/app.js. What I'd like to do is change the make file such that it stores the output in the dist directory, so src/index.md is converted to dist/index.html, src/assets/stylesheets/app.less compiled to dist/assets/stylesheets/app.css and and src/assets/scripts/app.coffee becomes dist/assets/scripts/app.js.
So I changed the makefile as follows:
sources = $(shell find src -type f)
t0 := $(subst src/,dist/,$(sources:.md=.html))
t1 := $(t0:.md=.html)
t2 := $(t1:.less=.css)
targets := $(t2:.coffee=.js)
targetdirs = $(dir $(targets))
all: $(targets)
%.html: %.md
mkdir -p $(targetdirs)
cp $< $#
%.css: %.less
mkdir -p $(targetdirs)
cp $< $#
%.js: %.coffee
mkdir -p $(targetdirs)
cp $< $#
Now make fails with the following:
make: *** No rule to make target `dist/assets/scripts/app.js', needed by `all'. Stop.
Most examples I can find, is limited to a single directory, or compiles multiple source files into a single target.
How would one achieve this without knowing the contents of the source directory?
Environment:
GNU Make 3.81
OS X 10.11.1
In make pattern rules, the stems represented by the patterns in the target and prerequisite must match exactly. Take this rule:
%.html: %.md
mkdir -p $(targetdirs)
cp $< $#
If the target make wants to build is dist/index.html, then the stem is dist/index. So, it will look for the prerequisite dist/index.md. Which doesn't exist. So make ignores that pattern rule as not matching, and continues to look for more implicit rules that might match... and doesn't find any so it fails.
You have to fix your rules so that the change in directory is reflected in the pattern:
dist/%.html: src/%.md
mkdir -p $(#D)
cp $< $#
(I'm not sure why you're creating all directories in every recipe instead of just the current one). Now the stem for dist/index.html is just index, and the prerequisite matches src/index.md and it will work.

Run make in each subdirectory

I have a directory (root_dir), that contains a number of sub-directories (subdir1, subdir2, ...).
I want to run the make in each directory in root_dir, using a Makefile placed in it.
(Obviously supposed that each of subdir... has inside its own Makefile).
So there are essentially two questions:
How to get a list of directories in Makefile (automatically)?
How to run make for each of the directories inside a make file?
As I know in order to run make in a specific directory I need to do the following:
$(MAKE) -C subdir
There are various problems with doing the sub-make inside a for loop in a single recipe. The best way to do multiple subdirectories is like this:
SUBDIRS := $(wildcard */.)
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $#
.PHONY: all $(SUBDIRS)
(Just to point out this is GNU make specific; you didn't mention any restrictions on the version of make you're using).
ETA Here's a version which supports multiple top-level targets.
TOPTARGETS := all clean
SUBDIRS := $(wildcard */.)
$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(MAKECMDGOALS)
.PHONY: $(TOPTARGETS) $(SUBDIRS)
Try this :
SUBDIRS = foo bar baz
subdirs:
for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
This may help you link
Edit : you can also do :
The simplest way is to do:
CODE_DIR = code
.PHONY: project_code
project_code:
$(MAKE) -C $(CODE_DIR)
The .PHONY rule means that project_code is not a file that needs to be
built, and the -C flag indicates a change in directory (equivalent to
running cd code before calling make). You can use the same approach
for calling other targets in the code Makefile.
For example:
clean:
$(MAKE) -C $(CODE_DIR) clean
Source
This is another approach to MadScientist's answer. .PHONY is a GNU-specific feature that can be used to force make into recursing into each subdirectory. However, some non-GNU versions of make do not support .PHONY, so an alternative is a force target.
4.7 Rules without Recipes or Prerequisites
If a rule has no prerequisites or recipe, and the target of the rule
is a nonexistent file, then make imagines this target to have been
updated whenever its rule is run. This implies that all targets
depending on this one will always have their recipe run.
An example will illustrate this:
clean: FORCE
rm $(objects)
FORCE:
Here the target ‘FORCE’ satisfies the special conditions, so the
target clean that depends on it is forced to run its recipe. There is
nothing special about the name ‘FORCE’, but that is one name commonly
used this way.
As you can see, using ‘FORCE’ this way has the same results as using
‘.PHONY: clean’.
Using ‘.PHONY’ is more explicit and more efficient. However, other
versions of make do not support ‘.PHONY’; thus ‘FORCE’ appears in many
makefiles. See Phony Targets.
The following is a minimal example that recurses make into each subdirectory, each of which presumably contains a Makefile. If you simply run make, only the first subdirectory, which is non-deterministic, is processed. You may also run make subdir1 subdir2 ....
# Register all subdirectories in the project's root directory.
SUBDIRS := $(wildcard */.)
# Recurse `make` into each subdirectory.
$(SUBDIRS): FORCE
$(MAKE) -C $#
# A target without prerequisites and a recipe, and there is no file named `FORCE`.
# `make` will always run this and any other target that depends on it.
FORCE:
Here is another example with top-level phony targets: all and clean. Note that the all and clean targets, passed from command-line via $(MAKECMDGOALS), are handled by each subdirectory's all and clean targets respectively.
# Register all subdirectories in the project's root directory.
SUBDIRS := $(wildcard */.)
# Top-level phony targets.
all clean: $(SUBDIRS) FORCE
# Similar to:
# .PHONY: all clean
# all clean: $(SUBDIRS)
# GNU's .PHONY target is more efficient in that it explicitly declares non-files.
# Recurse `make` into each subdirectory
# Pass along targets specified at command-line (if any).
$(SUBDIRS): FORCE
$(MAKE) -C $# $(MAKECMDGOALS)
# Force targets.
FORCE:
You can also define a function in the Makefile (also you of course need an additional makefile in each subdirectory). This is shell-dependent, but can be useful:
define FOREACH
for DIR in packages/*; do \
$(MAKE) -C $$DIR $(1); \
done
endef
.PHONY: build
build:
$(call FOREACH,build)
.PHONY: clean
clean:
$(call FOREACH,clean)
.PHONY: test
test:
$(call FOREACH,test)
Only a small icing on the cake after MadScientist's answer in order to make all the individual targets in the sub-directories available from the top level (you will need to have the SUBDIRS variable defined in order to use the following snippet – you can use MadScientist's answer for that):
# Make all the individual targets in the sub-directories available from the top
# level; as in, for instance, `make foo/my_program` or `make bar/clean`
$(foreach __dir__,$(SUBDIRS),$(__dir__)/%):
#$(MAKE) -C '$(#D)' '$(#F)'
With the code above you can run, for instance,
make foo/my_program
or
make bar/clean
Furthermore, by pasting the code above you can even use an individual target from a sub-directory as a prerequisite for a target in the top level. For example:
my_target: my_subdirectory/my_prerequisite
'my_subdirectory/my_prerequisite' > 'my_target'
…With the example above, launching make my_target from the top level will first build the my_subdirectory/my_prerequisite program, then the latter will be run for building the my_target file.
Since I was not aware of the MAKECMDGOALS variable and overlooked that MadScientist has its own implementation of multiple top-level targets, I wrote an alternative implementation. Maybe someone find it useful.
SUBDIRS := $(wildcard */.)
define submake
for d in $(SUBDIRS); \
do \
$(MAKE) $(1) --directory=$$d; \
done
endef
all:
$(call submake,$#)
install:
$(call submake,$#)
.PHONY: all install $(SUBDIRS)
There is a library called prorab for GNU make which supports inclusion of standalone makefiles in subdirectories.
Some info on github: https://github.com/cppfw/prorab/blob/master/wiki/HomePage.adoc
Basically, with prorab invoking all makefiles in subdirectories looks like this:
include prorab.mk
$(eval $(prorab-build-subdirs))
In reference to https://stackoverflow.com/posts/17845120/revisions
This is what I learned from that post.
Top Level Makefile
# set the default goal.
# I want the default to really just dump contents of dirs
# as a stub. For instance, I don't want it to
# push code or
.DEFAULT_GOAL := deploy
TOPTARGETS := all clean
SUBDIRS := docs src
$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
echo "make arg is" $(MAKECMDGOALS)
$(MAKE) -C $# $(MAKECMDGOALS)
SUBCLEAN = $(addsuffix .clean,$(SUBDIRS))
clean: $(SUBCLEAN)
$(SUBCLEAN): %.clean:
$(MAKE) -C $* clean
deploy:
echo do deploy stub
The src/ and docs/ common to this Makefile directory, all have a corresponding Makefile.
Here is an example of the docs setup:
# set the default goal.
.DEFAULT_GOAL := list_docs
list_docs:
ls -l
clean:
echo "docs: make clean"
-rm "*.backup"
I did this a little different than any of the answers because I didn't want to have to define each possible make target
SUBDIRS := $(patsubst %/,%,$(wildcard */))
.PHONY: all $(MAKECMDGOALS) $(SUBDIRS)
$(MAKECMDGOALS) all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $# $(MAKECMDGOALS)

In Makefiles, how to re-enable --print-directory in a sub-directory when --no-print-directory has been set?

First, let me say that I am aware of the cons of using recursive Makefiles. So if you are here just to tell me don't use it, please don't.
Imagine this directory structure:
rootdir
`-- subdir
|-- a
|-- b
`-- c
Let's say the Makefile on rootdir reads like this:
.PHONY: all
all:
# build some stuff
$(MAKE) -C subdir
and the one in subdir reads like this:
.PHONY: all
all:
# nothing here except redirecting make to each of the subdirectories
$(MAKE) -C a
$(MAKE) -C b
$(MAKE) -C c
and another Makefile in each of a, b and c folders building something.
Since the Makefile in subdir serves no purpose except redirecting make, I want make not to print: Entering directory rootdir/subdir and Leaving directory rootdir/subdir to clean up the output a bit.
On the other hand, since there are commands being executed in the subfolders a, b and c, I do want make to print these outputs. Here's what I thought would work:
rootdir's Makefile:
.PHONY: all
all:
# build some stuff
$(MAKE) --no-print-directory -C subdir
subdir's Makefile:
.PHONY: all
all:
# nothing here except redirecting make to each of the subdirectories
$(MAKE) --print-directory -C a
$(MAKE) --print-directory -C b
$(MAKE) --print-directory -C c
The problem is, once the --no-print-directory is given to make when calling make for subdir, --print-directory doesn't enable it again when calling make for a, b or c.
So my question is, how can I re-enable printing directories when a parent make has disabled it?
Make command line flags get communicated to sub-makes via MAKEFLAGS variable. You may like to replace --no-print-directory (if any) from MAKEFLAGS with w manually before invoking the sub-makes:
${MAKE} MAKEFLAGS="$(subst --no-print-directory,w,${MAKEFLAGS})" -C ...

make wildcard subdirectory targets

I have a "lib" directory in my applications main directory, which contains an arbitrary number of subdirectories, each having its own Makefile.
I would like to have a single Makefile in the main directory, that calls each subdirectory's Makefile. I know this is possible if I manually list the subdirs, but I would like to have it done automatically.
I was thinking of something like the following, but it obviously does not work. Note that I also have clean, test, etc. targets, so % is probably not a good idea at all.
LIBS=lib/*
all: $(LIBS)
%:
(cd $#; $(MAKE))
Any help is appreciated!
The following will work with GNU make:
LIBS=$(wildcard lib/*)
all: $(LIBS)
.PHONY: force
$(LIBS): force
cd $# && pwd
If there might be something other than directories in lib, you could alternatively use:
LIBS=$(shell find lib -type d)
To address the multiple targets issue, you can build special targets for each directory, then strip off the prefix for the sub-build:
LIBS=$(wildcard lib/*)
clean_LIBS=$(addprefix clean_,$(LIBS))
all: $(LIBS)
clean: $(clean_LIBS)
.PHONY: force
$(LIBS): force
echo make -C $#
$(clean_LIBS): force
echo make -C $(patsubst clean_%,%,$#) clean
There is also a way of listing sub-directories with gmake commands only, without using any shell commands:
test:
#echo $(filter %/, $(wildcard lib/*/))
This will list all sub-directories with trailing '/'. To remove it you can use the substitute pattern:
subdirs = $(filter %/, $(wildcard lib/*/))
test:
#echo $(subdirs:%/=%)
Then to actually create rules executing makefiles in each sub-directory you can use a small trick - a phony target in a non-existent directory. I think in this case an example will tell more than any explanation:
FULL_DIRS =$(filter %/, $(wildcard lib/*/))
LIB_DIRS =$(FULL_DIRS:%/=%)
DIRS_CMD =$(foreach subdir, $(LIB_DIRS), make-rule/$(subdir))
make-rule/%:
cd $* && $(MAKE)
all: DIRS_CMD
Basically, target 'all' lists all sub-directories as prerequisites. For example, if LIB_DIRS contained lib/folder1 lib/folder2 then the expansion would look like this:
all: make-rule/lib/folder1 make-rule/lib/folder2
Then 'make', in order to execute rule 'all', tries to match each prerequisite with an existing target. In this case the target is 'make-rule/%:', which uses '$*' to extract the string after 'make-rule/' and uses it as argument in the recipe. For example, the first prerequisite would be matched and expanded like this:
make-rule/lib/folder1:
cd lib/folder1 && $(MAKE)
What if you want to call different targets than all in an unknown number of subdirectories?
The following Makefile uses macros so create a forwarding dummy-target for a number of subdirectories to apply the given target from the command line to each of them:
# all direct directories of this dir. uses "-printf" to get rid of the "./"
DIRS=$(shell find . -maxdepth 1 -mindepth 1 -type d -not -name ".*" -printf '%P\n')
# "all" target is there by default, same logic as via the macro
all: $(DIRS)
$(DIRS):
$(MAKE) -C $#
.PHONY: $(DIRS)
# if explcit targets where given: use them in the macro down below. each target will be delivered to each subdirectory contained in $(DIRS).
EXTRA_TARGETS=$(MAKECMDGOALS)
define RECURSIVE_MAKE_WITH_TARGET
# create new variable, with the name of the target as prefix. it holds all
# subdirectories with the target as suffix
$(1)_DIRS=$$(addprefix $(1)_,$$(DIRS))
# create new target with the variable holding all the subdirectories+suffix as
# prerequisite
$(1): $$($1_DIRS)
# use list to create target to fullfill prerequisite. the rule is to call
# recursive make into the subdir with the target
$$($(1)_DIRS):
$$(MAKE) -C $$(patsubst $(1)_%,%,$$#) $(1)
# and make all targets .PHONY
.PHONY: $$($(1)_DIRS)
endef
# evaluate the macro for all given list of targets
$(foreach t,$(EXTRA_TARGETS),$(eval $(call RECURSIVE_MAKE_WITH_TARGET,$(t))))
Hope this helps. Really helpfull when dealing with paralelism: make -j12 clean all in a tree with makefiles having these targets... As always: playing with make is dangerous, different meta-levels of programming are too close together ,-)

Resources