views:

189

answers:

2

I need to get some external data to form an output file name in a Makefile.

In GNU Make I can do something like this :

NAME=$(shell some_commands)
$(NAME).out: file.in
     compile "$<" -o "$@"

Some other rules have $(NAME).out as a prerequisite so I need $(NAME).out as a target. I can't use substitution+quotes here as this is not interpreted by a shell.

I see from the BSD Make man page that on Mac OS X I should be able to do something like this (I could not test it yet, though) :

NAME!=some_commands
$(NAME).out: file.in
     compile "$<" -o "$@"

Is there a hack to do this kind of things in a portable way?

I do not want to generate the Makefile and clutter the build process. I though of includes, but they also have a different syntax across Make versions.

+2  A: 

I can propose several tricks. All them are based on the outsourcing the execution of some_commands to a rule body, and ensuring that their result will be utilized properly.

  1. That's my favourite. Include a makefile, which you create on your own, that contains the rule for $(NAME). At the beginning of processing it will be re-created and the proper target will appear. The thing is that the process of creation of this makefile is done within shell commands:

    include aux_makefile
    .PHONY aux_makefile
    aux_makefile:
        NAME=`some_commands` ; ( \
            echo $$NAME.out: file.in ; \
            echo "\t rm aux_makefile" ; \
            echo "\t compile ..."  ) >$@
    
  2. A variation of first one: make a wrapper makefile that first writes the auxilliary makefile and then recursively calls the original makefile, the auxilliary makefile being included into it. No special build target needed.

  3. Make a dummy target with a static name; it will represent $(NAME).out. I.e.:

    all: dummy    # Instead of all: $(NAME).out
    dummy: file.in
       NAME=`some_commands`; compile "$<" -o "$$NAME"
       touch $@
    
Pavel Shved
Ugh. Very clever, but still...
dmckee
Eventually I just did a variation of method 3 (I want it to be the simplest possible and easy modifiable by others !!). I simply made a static rule on which other rules depend, and a copy with the appropriate name as an user output.
Piotr Lesnicki
@Piotr, that would work too, but the key thing here is that this rule should not be `.PHONY`, and it should `touch` the file equal to its name, so that it avoids unnecessary recompilation.
Pavel Shved
@Pavel: yeah, exactly, I have a non-`.PHONY` rule, and so a static output file name for the build process, and then another file name with the required name. So not totally pretty, but avoids recompilation, and all files have significant content.But I once also rediscovered the hack with `touch` trying to do the minimal amount of LaTeX recompilations :)
Piotr Lesnicki
+2  A: 

This is kind of clumsy, and I don't know enough about different versions of Make to know if it's really portable, but it might work, and it satisfies your strict conditions (no generating the makefile, no 'include', and things can depend on $(NAME).out). If there is only one target you intend to make from the command line (say, 'all') then you can write it like this:

ifeq ($(origin NAME), undefined)
all:
    @$(MAKE) -s all NAME=some_commands_using_substituion_and_quotes
else
all: some_preqs_maybe_including_NAME
    do_other_things
endif

When you run Make, NAME is undefined, so the rule runs Make again with NAME defined-- since the command IS interpreted by the shell. (The "@" and "-s" are just to make it quieter.) And if you try to make other targets "by hand" you'll have a lot of trouble since $(NAME) will evaluate to nothing.

Beta
Oh, that's also clever, but I'm not quite sure it's portable, from what I see on the BSD Make Manual, directives have a different syntax (`.if` ...).
Piotr Lesnicki
As usual, better than mine :-)
Pavel Shved
@Pavel, oh stop it. You gave three solutions, I gave one that puts a serious limitation on the use of the makefile and probably isn't even portable (which was the whole point). Say, maybe we could write a makefile that detects which version is running it, then defines an IFTHEN function... Or maybe we could just write a virus to destroy all non-GNU makefiles, it would be less horrible.
Beta