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

后端 未结 5 1904
别那么骄傲
别那么骄傲 2021-02-19 13:14

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 targ

相关标签:
5条回答
  • 2021-02-19 13:53

    Thinking about this and tried the following:

    TEST := $(shell echo $$RANDOM)
    
    test : $(TEST)
    
    $(TEST):
    <tab>@echo tada $(TEST)
    

    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?

    0 讨论(0)
  • 2021-02-19 13:55

    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
    
    0 讨论(0)
  • 2021-02-19 13:58

    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:

    .SUFFIXES:
    
    PUBLIC_TARGETS = all debug release clean
    REAL_MAKEFILE = private.mak
    
    define invoke_make
     $(1): $(REAL_MAKEFILE)
        $(MAKE) -f $(REAL_MAKEFILE) $(1)
    endef
    
    $(foreach target, $(PUBLIC_TARGETS), $(eval $(call invoke_make,$(target))))
    
    .PHONY: $(PUBLIC_TARGETS)
    

    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.

    0 讨论(0)
  • 2021-02-19 14:13

    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.

    0 讨论(0)
  • 2021-02-19 14:13

    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:

    bin_debug/%.o   : CPPFLAGS += -DDEBUG
    bin_debug/%.o   : %.c
        $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
    bin_release/%.o : CPPFLAGS += -DRELEASE
    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)
    

    so make bin_debug/foo.o will get CPPFLAGS 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:

    debug    : CPPFLAGS+=-DDEBUG
    release  : CPPFLAGS+=-DRELEASE
    config01 : CPPFLAGS+=-DSOMETHING
    config02 : CPPFLAGS+=-DSOMETHINGELSE
    TARGETS = debug release config01 config02
    OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
    define TARGET_template
     bin_$(1)/%.o : %.c
        $$(CC) $$(CFLAGS) $$(CPPFLAGS) -c -o $@ $<
     $(1): $(addprefix bin_$(1)/,$(OBJS))
     # other TARGET-specific stuff here
    endef
    $(foreach tgt,$(TARGETS),$(eval $(call TARGET_template,$(tgt))))
    

    But still doesn't fix the situation of make bin_debug/foo.o -- still doesn't get CPPFLAGS.

    So, instead of making target-specific variable-value like debug: CPPFLAGS+=... you could have a variable that is specific to the target, like CPPFLAGS_debug, then add to each rule:

    CPPFLAGS_debug    = -DDEBUG
    CPPFLAGS_release  = -DRELEASE
    CPPFLAGS_config01 = -DSOMETHING
    CPPFLAGS_config02 = -DSOMETHINGELSE
    TARGETS = debug release config01 config02
    OBJS = foo.o bar.o main.o # or $(SRCS:.o=.c)
    define TARGET_template
     bin_$(1)/%.o : CPPFLAGS+=$$(CPPFLAGS_$(1))
     bin_$(1)/%.o : %.c
        $$(CC) $$(CFLAGS) $$(CPPFLAGS) -c -o $$@ $$<
     $(1): $(addprefix bin_$(1)/,$(OBJS))
     # other TARGET-specific stuff here
    endef
    $(foreach tgt,$(TARGETS),$(eval $(call TARGET_template,$(tgt))))
    

    Beware; above may need more $$(...)s, untested.

    Problems? Better way?

    0 讨论(0)
提交回复
热议问题