2
votes

I'm using GNU Make and attempting to design Makefiles using non-recursive approach. The issue I have - there doesn't seem to be a way to limit the scope of variables in different Makefiles.

For example, if I have two Makefiles for modules libA and libB

libA Makefile.inc:
src_dir := libA/src
inc_dir := libA/inc

libB Makefile.inc:
src_dir := libB/src
inc_dir := libB/inc

Then when I include the above Makefiles into a master Makefile

include libA/Makefile.inc
include libB/Makefile.inc

Values in variables src_dir and inc_dir are overwritten by the very last Makefile that was included. OK this is probably expected since variables are global in scope here, and this messes up build commands that use those variables, i.e. build command for libA finds variable values for libB.

A way around it is to create unique variables for each Makefile

libA Makefile.inc:
src_dir_libA := libA/src
inc_dir_libA := libA/inc

libB Makefile.inc:
src_dir_libB := libB/src
inc_dir_libB := libB/inc

This solves the issue, but it's a bit awkward to use, since each variable has to be renamed. Does anyone know if there is a better way to solve this, e.g. if GNU Make has some notion of scope or namespace? I've looked at documentation, but can't seem to find anything of that sort. There are target-specific variables, but they appear to have nasty side-effects.

OK, just to be very specific, below is an example Makefile

dep_all :=
all:

# Begin Makefile.inc for project1
# ------------------------------------------------------------------------------
$(foreach var,$(filter local_%,$(.VARIABLES)),$(eval $(var) := ))
local_src_dir := project1/src
local_obj_dir := project1/obj
local_src := $(local_src_dir)/fileA.c $(local_src_dir)/fileB.c
local_obj := $(patsubst $(local_src_dir)/%.c,$(local_obj_dir)/%.o,$(local_src))

dep_all += $(local_src)
dep_all += $(local_obj)

$(info local_obj="$(local_obj)")
$(local_obj): $(local_obj_dir)/%.o: $(local_src_dir)/%.c
    gcc -L$(local_obj_dir) -c -o $@ $<
# ------------------------------------------------------------------------------
# End Makefile.inc for project1


# Begin Makefile.inc for project2
# ------------------------------------------------------------------------------
$(foreach var,$(filter local_%,$(.VARIABLES)),$(eval $(var) := ))
local_src_dir := project2/src
local_obj_dir := project2/obj
local_src := $(local_src_dir)/fileX.c $(local_src_dir)/fileY.c
local_obj := $(patsubst $(local_src_dir)/%.c,$(local_obj_dir)/%.o,$(local_src))

dep_all += $(local_src)
dep_all += $(local_obj)

$(info local_obj="$(local_obj)")
$(local_obj): $(local_obj_dir)/%.o: $(local_src_dir)/%.c
    gcc -L$(local_obj_dir) -c -o $@ $<
# ------------------------------------------------------------------------------
# End Makefile.inc for project2

$(info dep_all="$(dep_all)")

.PHONY: all
all: $(dep_all)

.PHONY: clean
clean:
    rm -f project1/obj/* project2/obj/*

If I run it then the -L<object_path> option passed to gcc contains value from the last included Makefile, i.e. when building project1, it runs gcc with -Lproject2/obj, which is not the right object path for this project. This is the problem I'm trying to solve.

mkdir -p project1/{src,obj} project2/{src,obj}
touch project1/src/{fileA.c,fileB.c} project2/src/{fileX.c,fileY.c}

$ make
local_obj="project1/obj/fileA.o project1/obj/fileB.o"
local_obj="project2/obj/fileX.o project2/obj/fileY.o"
dep_all=" project1/src/fileA.c project1/src/fileB.c project1/obj/fileA.o project1/obj/fileB.o project2/src/fileX.c project2/src/fileY.c project2/obj/fileX.o project2/obj/fileY.o"
gcc -Lproject2/obj -c -o project1/obj/fileA.o project1/src/fileA.c
gcc -Lproject2/obj -c -o project1/obj/fileB.o project1/src/fileB.c
gcc -Lproject2/obj -c -o project2/obj/fileX.o project2/src/fileX.c
gcc -Lproject2/obj -c -o project2/obj/fileY.o project2/src/fileY.c
2
The obvious and reasonably standard solution is recursive make.tripleee
Have a look at my implementation of non-recursive makefiles: github.com/igagis/prorab The approach is that all those 'local' variables are named with this_ prefix and in the beginning of each makefile all variables with this_ prefix are cleared, look here: github.com/igagis/prorab/blob/master/wiki/…igagis
Another way I've seen is prefix each local variable with $(DIR), where $(DIR) is derived from the directory name. ( BTW having worked with non-recursive makefiles for a bit now, I have to say I support @tripleee's suggestion over this one... )HardcoreHenry
recursive make suffers from all kind of problems, why use it?igagis

2 Answers

1
votes

The solution is to name all your variables local to a specific file with some prefix. For example, I use this_ prefix.

Then, in the beginning of each submakefile those variales which have this_ prefix can be cleared as follows:

$(foreach var,$(filter this_%,$(.VARIABLES)),$(eval $(var) := ))

PS. I use this approach in my implementation of non-recursive makefiles, clearing the variables described in the WIKI here: https://github.com/cppfw/prorab

1
votes

I think I've just figured out a solution. I was reading "The GNU Make Book" and it states a side effect of target specific variables

Target-specific variables apply not just to a target, but also to all that target’s prerequisites, as well as all their prerequisites, and so on. A target-specific variable’s scope is the entire tree of targets, starting from the target for which the variable was defined.

This is not the behaviour I want, however starting with GNU Make 3.82 there is support for private target specific variables

A target-specific variable is normally defined for a target and all its prerequisites. But if the target-specific variable is prefixed with the keyword private, it is defined only for that target, not its prerequisites.

So the following Makefile seems to work correctly for those private variables

dep_all :=
all:

# Begin Makefile.inc for project1
# ------------------------------------------------------------------------------
inc_dir := project1/inc
src_dir := project1/src
obj_dir := project1/obj
src := $(src_dir)/fileA.c $(src_dir)/fileB.c
obj := $(patsubst $(src_dir)/%.c,$(obj_dir)/%.o,$(src))

# These flags will be overwritten by another Makefile
CFLAGS      := -I$(inc_dir)

# These flags will be private and not be overwritten by another Makefile
CFLAGS_priv := -I$(inc_dir) -L$(obj_dir)

dep_all += $(src)
dep_all += $(obj)

# Private target specific variables
$(obj): private CFLAGS_priv:=$(CFLAGS_priv)

$(obj): $(obj_dir)/%.o: $(src_dir)/%.c
    gcc $(CFLAGS) $(CFLAGS_priv) -c -o $@ $<
# ------------------------------------------------------------------------------
# End Makefile.inc for project1


# Begin Makefile.inc for project2
# ------------------------------------------------------------------------------
inc_dir := project2/inc
src_dir := project2/src
obj_dir := project2/obj
src := $(src_dir)/fileX.c $(src_dir)/fileY.c
obj := $(patsubst $(src_dir)/%.c,$(obj_dir)/%.o,$(src))

# These flags will be overwritten by another Makefile
CFLAGS      := -I$(inc_dir)

# These flags will be private and not be overwritten by another Makefile
CFLAGS_priv := -I$(inc_dir) -L$(obj_dir)

dep_all += $(src)
dep_all += $(obj)

# Private target specific variables
$(obj): private CFLAGS_priv:=$(CFLAGS_priv)

$(obj): $(obj_dir)/%.o: $(src_dir)/%.c
    gcc $(CFLAGS) $(CFLAGS_priv) -c -o $@ $<
# ------------------------------------------------------------------------------
# End Makefile.inc for project2


.PHONY: all
all: $(dep_all)

.PHONY: clean
clean:
    rm -f project1/obj/* project2/obj/*

The output is what I wanted, since now for each project I can define private CFLAGS_priv variable that sets -I<dir>, -L<dir> paths and it won't be overwritten by other Makefiles.

$ make
gcc -Iproject2/inc -Iproject1/inc -Lproject1/obj -c -o project1/obj/fileA.o project1/src/fileA.c
gcc -Iproject2/inc -Iproject1/inc -Lproject1/obj -c -o project1/obj/fileB.o project1/src/fileB.c
gcc -Iproject2/inc -Iproject2/inc -Lproject2/obj -c -o project2/obj/fileX.o project2/src/fileX.c
gcc -Iproject2/inc -Iproject2/inc -Lproject2/obj -c -o project2/obj/fileY.o project2/src/fileY.c

I'm hoping this will resolve all issues I had and I don't have to use recursive make with it's various associated pitfalls.