I have some ancillary targets in a makefile that I want to restrict for internal or "private" use (only) inside the makefile. That is, I want to be able to specify these targets as dependencies from within the makefile, but I want to prevent the target from being specified as a build goal from the command line. Somewhat analogous to a private
function from OOP: the target is harmful (or simply doesn't make sense) to build separately.
I wish there were a special-target .HIDDEN
or .PRIVATE
or something that did this, akin to what .PHONY
does for non-file targets, but I don't think this exists. The private
keyword is only for variables.
What is a good/general/elegant way to protect a target for internal/private use only?
The best workaround that I could come up with is to check $(MAKECMDGOALS)
for "unacceptable" targets, then error-out if specified; this seems inelegant. I'm sure the makefile could be rewritten to avoid this situation -- perhaps a superior solution -- but that's not practical here.
Below the cut-line... here's a contrived example for illustration.
Though I'm looking for a general solution, one example of targets that are harmful as individual/primary goal is with inheriting of target-specific variable values:
override CFLAGS += -Wall
all : debug
%.o : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
debug : CFLAGS += -g3 -O0
release : CFLAGS += -O3
debug : CPPFLAGS += -DDEBUG
release : CPPFLAGS += -DRELEASE
debug release : foo.o bar.o main.o
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
clean:
-rm -f *.o debug release
.PHONY: all clean
Implicit rule duplicated (unnecessary) for illustration. With the goal of debug
or release
, foo.o
and others will inherit respective CFLAGS
and CPPFLAGS
-- If one does make clean debug
all objects will be consistent. But for example if someone builds foo.o
separately, it will fail to inherit the appropriate flags; e.g., make clean foo.o debug
you'll get foo.o
built with default CFLAGS
; then it doesn't need to be updated when building debug
, so it will be linked with other objects with different optimizations or different macro settings. It will probably work in this case, but it's not what was intended. Marking foo.o
, etc. as illegal goals would prevent this.
EDIT:
It's very clear that my example (above) was not a good choice for my more-general question: hiding targets was not the best way to fix an issue with my example. Here's a modified example that illustrates the modified question "How to enforce target-specific values?" -- it builds on commentary from @Michael, @Beta, @Ross below, and allows posing and answering this more limited scenario.
As described in previous responses below, it's a much better idea in this case to create objects that have different build flags in separate locations. e.g.,
bin_debug/%.o : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
bin_release/%.o : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
DEBUG_OBJS = $(addprefix bin_debug/,$OBJS)
RELEASE_OBJS = $(addprefix bin_release/,$OBJS)
debug : $(DEBUG_OBJS)
release : $(RELEASE_OBJS)
debug release :
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
Pattern rule duplicated because I think it has to be (multiple "pattern targets" (%
) convince make
all targets are built at once with one recipe; see SO questions this and this).
So now, add in target-specific flags:
debug : CPPFLAGS += -DDEBUG
release : CPPFLAGS += -DRELEASE
But this still suffers:
make bin_debug/foo.o
will not get the CPPFLAGS
from debug
. I've accepted @Michael's answer below as it got me thinking about the problem in a more helpful way, but also answered some of my own rhetorical questions below.
The problem you are trying to solve is legitimate but you are heading on the worse possible path to solve it.
Declaring private targets does not make any sense
When we write a Makefile, we are describing a compilation job in terms of targets, sources and recipes. The advancement of this job is described by the set of targets which are already built. Now you are accurately observing that the sequence
will produce objects whose format is inconsistent with
foo.o
thus leaving your build directory in an inconsistent state. But it is very wrong to deduce that the user should not be able to constructfoo.o
explicitly. Consider the following sequence:Since
make
sees thatfoo.o
it will resume its task where it was at and leftfoo.o
untouched while compiling subsequent units with different flags, leaving the build directory the same inconsistent state as in the first scenario.Hence, if we could implement private targets in Makefiles, this would be ineffective and could convey a false sense of security, which is even worse than insecurity by itself. Also the solution you imagined annihilates one of the most important advantages of using Makefiles over shell scripts: Make makes it easy to continue an interrupted task where it was at.
I documented some other aspects of using Makefiles in relation to the set of targets already built in my answer to the question “What is the purpose of linking object files separately in a Makefile?”.
Another solution to your problem
To address the issue of compilation flags inconsistency, we can arrange to store built targets into a special directory, depending on the compilation flags used. Implementing this would fix the issue without forcing us to resign upon the ease of resuming an interrupted compilation job.
Here is an implementation roadmap:
release
andbuild
.Note. In my opinion, the BSD variant of
make
has a much nicer support for writing targets in a special directory, see my answer to the question “How to write a Makefile using different directories for targets and sources”. Generally I prefer the BSD variant ofmake
because its documentation is short and to the point and it enjoys a lot of useful advanced examples, since operating system build and ports build in the BSD world are orchestrated by this program.One solution to the problem is to migrate the
CPPFLAGS
to the pattern rules (e.g.,bin_debug/%.o: CPPFLAGS...
) instead of the regular rule (debug: CPPFLAGS...
), final result:so
make bin_debug/foo.o
will getCPPFLAGS
including-DDEBUG
.Now, lets say you have >>2 rules: debug, release, config01, config02, config03, ... each with their own
CPPFLAGS
.One way might be to continue reduplicating all of the pattern rules, but that gets annoying if anything has to change. Furthermore it's not really possible to use in a
foreach
. This seems handy:But still doesn't fix the situation of
make bin_debug/foo.o
-- still doesn't getCPPFLAGS
.So, instead of making target-specific variable-value like
debug: CPPFLAGS+=...
you could have a variable that is specific to the target, likeCPPFLAGS_debug
, then add to each rule:Beware; above may need more
$$(...)
s, untested.Problems? Better way?
You kind of can define private targets by starting their name with two hyphens.
You can call
make public-target
butmake --private-target
will complain about an unknown option:Thinking about this and tried the following:
then doing a
make test
on command line seems to work and I think it would be difficult to get the result without using the test target. Maybe this path can help?I don't think there's any "elegant" way to have targets somehow made private. I think the only solution that could be called elegant would be to rewrite your makefile so that it doesn't matter what target users invoke, as Beta suggests. It would also have the advantage of making your makefile more maintainable and easier to understand.
A not so elegant but fairly simple way to make targets "private" would be to rename the makefile to something other than one of the default names. Then put a new makefile in it's place that invokes the "private" makefile to do it's work. Something like:
Obviously this doesn't prevent a determined user from invoking "private" targets, but hopefully it makes it clear that they shouldn't be doing this. That's all making things private in object-oriented languages does anyways. It's always possible for a sufficiently determined user to bypass it.