Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a target "private" in GNU make for internal use only? OR: how to best enforce target-specific variable-values?

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.

like image 998
hoc_age Avatar asked Sep 26 '14 16:09

hoc_age


2 Answers

You kind of can define private targets by starting their name with two hyphens.

--private-target:
    @echo private

public-target: --private-target
    @echo public

You can call make public-target but make --private-target will complain about an unknown option:

$ make public-target
private
public

$ make --private-target
/Library/Developer/CommandLineTools/usr/bin/make: unrecognized option `--private-target'

$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0
like image 53
udondan Avatar answered Oct 29 '22 00:10

udondan


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

make clean
make foo.o
make debug

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 construct foo.o explicitly. Consider the following sequence:

make clean
# Wait for foo.o being compiles and
#  interrupt the build job with a signal
make debug

Since make sees that foo.o it will resume its task where it was at and left foo.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:

  1. Identify build profiles, here you have release and build.
  2. Choose which compilation to use for each build profile.
  3. Choose in which directory to store built targets for each build profile.
  4. Write your Makefile so that built targets are stored in the directories you choosed. Please refer Gnu make - how to get object files in separate subdirectory.

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 of make 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.

like image 32
Michaël Le Barbier Avatar answered Oct 29 '22 01:10

Michaël Le Barbier