Matthew Mitchell
Matthew Mitchell

Reputation: 5393

How to use the include directive in a makefile for a specific target

I want to use the include directive only for a specific target. I do not want to run the other makefiles when the target is not needed because it means the makefiles are generated needlessly.

So is there a way to conditionally use the include directive, which is conditional on a target? Or somehow to make the include directive a prerequisite of a target.

Here's what I have so far:

# Flags

INCDIR = $(CURDIR)/include
CFLAGS = -Wall -Wno-overflow -Wno-uninitialized -pedantic -std=c99 -I$(INCDIR) -O3
LFLAGS = -flat_namespace -dynamiclib -undefined dynamic_lookup

# Directory names

# Set vpath search paths

vpath %.h include
vpath %.c src
vpath %.o build
vpath %.d build

# Get files for the core library

CORE_FILES = $(wildcard src/*.c)
CORE_OBJS = $(patsubst src/%.c, build/%.o, $(CORE_FILES))
CORE_DEPS = $(CORE_OBJS:.o=.d)

# Core library target linking

core : $(CORE_OBJS) | bin
    $(CC) $(LFLAGS) -o bin/libcbitcoin.2.0.dylib $(CORE_OBJS)

# Include header prerequisites (How to do only for "core" target?)

include $(CORE_DEPS)

# Makefiles for header dependencies. 

$(CORE_DEPS): build/%.d: src/%.c | build
    rm -f $@; \
    $(CC) -I$(INCDIR) -MM $< -MT '$(@:.d=.o) $@' > $@

# Objects depend on directory

$(CORE_OBS) : | build

# Create build directory

build:
    mkdir build

# Create bin directory

bin:
    mkdir bin

# Core Compilation

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@

# Depencies require include/CBDependencies.h as a prerequisite

build/CBOpenSSLCrypto.o: include/CBDependencies.h

# Crypto library target linking

crypto : build/CBOpenSSLCrypto.o -lcrypto -lssl | bin
    $(CC) $(LFLAGS) -o bin/libcbitcoin-crypto.2.0.dylib build/CBOpenSSLCrypto.o -lcrypto -lssl

# Crypto library compile

build/CBOpenSSLCrypto.o: dependencies/crypto/CBOpenSSLCrypto.c
    $(CC) -c $(CFLAGS) $< -o $@

#Clean

clean:
    rm -f $(CORE_OBJS) $(CORE_DEPS) build/CBOpenSSLCrypto.o

As you should be able to tell I do not need to include the ".d" files for "crypto" but I do for "core" (default goal).

Thank you for any help.

Upvotes: 21

Views: 39774

Answers (4)

mcz
mcz

Reputation: 11

Let's say there are different dependencies for 'debug' and 'release' targets. There is also default 'all' target that builds 'release'. Dependencies definitions:

DBG_DEPS ::= $(addprefix $(DBG_OBJS_DIR)/, $(DEPS))
REL_DEPS ::= $(addprefix $(REL_OBJS_DIR)/, $(DEPS))
...
$(REL_OBJS_DIR)/%.o $(REL_OBJS_DIR)/%.d: %.cpp $(REL_OBJS_DIR)/.dirstamp
    $(info Compiling release: $<)
    @$(CXX) $(CXXFLAGS) $(REL_CXXFLAGS) -MMD -o $(basename $@).o -c $<
...
$(DBG_OBJS_DIR)/%.o $(DBG_OBJS_DIR)/%.d: %.cpp $(DBG_OBJS_DIR)/.dirstamp
    $(info Compiling debug: $<)
    @$(CXX) $(CXXFLAGS) $(DBG_CXXFLAGS) -MMD -o $(basename $@).o -c $<

So, for correct builds we would need to include one file for 'debug' and different for 'release'. Without conditional include every consecutive make clean would rebuild all the .o and .d files. Also make release would build debug .d and .o files.

Here's the result that works as expected:

ifeq (,$(MAKECMDGOALS))
-include $(REL_DEPS)
endif

ifneq (,$(findstring release,$(MAKECMDGOALS)))
-include $(REL_DEPS)
endif

ifneq (,$(findstring all,$(MAKECMDGOALS)))
-include $(REL_DEPS)
endif

ifneq (,$(findstring debug,$(MAKECMDGOALS)))
-include $(DBG_DEPS)
endif

This generates proper (almost, still needs adding .d next to .o targets with MT option) dependency files correct for given target (may be different) that react on headers and include them only when building for that target, and not for 'clean'.

Upvotes: 1

Robert Durkacz
Robert Durkacz

Reputation: 29

I can't help breaking the guidelines for what is a good answer.

My answer to the original question is in my opinion, no you cannot include rules that are dependant on the target -all rules are processed before targets are considered. This is a limitation of make (I guess). Then again, good point, there is MAKECMDGOALS, but is this not just a hack in the make utility itself?

The answer from Beta is reasonable and orthodox enough, but you can't call it clean even if it is the best that can be done. It won't work if make has not processed the particular target before and the appropriate build/*.d dependency file is not sitting there.

Upvotes: 2

Beta
Beta

Reputation: 99104

Make is not a procedural language, so treating it as one goes against the grain; your makefiles will be difficult to scale, and it can lead to subtle bugs.

There's a better way by Tom Tromey that's clean, efficient and scalable. The trick is to realize that you can build the dependency file in the same step as the object file. The dependencies simply tell Make when the object is due to be rebuilt; you don't need them when you first build the object, because Make knows that the object must be built. And if the dependencies change, that can only be because something in the source or the old dependencies has changed, so again Make knows that the object must be rebuilt. (This is not obvious, so it may take a little cogitation.)

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<

-include build/*.d

There's one more hitch: if you alter the code so as to remove a dependency -- and also remove that file -- you won't be able to rebuild, because the old dependency list will still demand a file which can no longer be found. The sophisticated solution is to process the dependency file so as to make each prerequisite (e.g. header) a target in its own right, with no commands, so that it can be assumed to be rebuilt when needed:

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<
    @cp build/$*.d build/$*.P
    @sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
            -e '/^$$/ d' -e 's/$$/ :/' < build/$*.P >> build/$*.d;
    @rm build/$*.P

A cruder method, but almost as foolproof, is to put in catch-all rules for headers and sources:

$(CORE_OBJS): build/%.o: src/%.c
    $(CC) -c $(CFLAGS) $< -o $@
    $(CC) -MM -MF build/$*.d $<

%.cc %.h:

EDIT:

To break down the new commands:

The -MM option tells gcc to produce a make rule for the object file, instead of preprocessing or compiling. The default is to send the rule to wherever it would send preprocessed output, which will usually be stdout.

The -MF option, used with -MM, specifies the output file. So -MM -MF build/$*.d will put the rule where we want it.

So the following two commands are (almost always) equivalent:

    $(CC) -MM -MF build/$*.d $<

    $(CC) -MM $< > build/$*.d

(I've left out the -I$(...) and the possibility of using the -MMD option, because both get a little complicated and are not really the point of the question.)

Upvotes: 21

John
John

Reputation: 3520

You can use MAKECMDGOALS.

ifeq (core,$(MAKECMDGOALS))
include $(CORE_DEPS)
endif

You could of course, use ifneq (,$(findstring core,$(MAKECMDGOALS))) if there was a possibility of more than one target.

Note: this is a 'quick and dirty' solution -- I agree with Beta that you shouldn't make this a common practice (this could get messy if you did it in a lot of makefiles...).

John

Upvotes: 3

Related Questions