Make: extract the target path segment that follows a known one - makefile

I am trying to optimize our build targets. In our current process we have separate targets for 32bit & 64bit build. Due to separate targets we have hundreds of targets in our build flow. Where we try to make it global i.e. one target for both 32-bit and 64-bit build. I'm trying to achieve this with the code below.
Target/Output directory for 32-bit build should be like:
/test/scratch/<client_name>/Linux/...
Target/Output directory for 64-bit build should be like:
/test/scratch/<client_name>/Linux-64bit/...
So based on above Target directory paths I am searching the string Linux using $(findstring) function and proceed with 32-bit command to run. Else it will run 64bit command as shown below.
RELEASE_FILES := $(TARGET_DIR)/build/test/csimtime.c
$(RELEASE_FILES): $(TGTDIRFILES)/%: %
ifneq (Linux,$(findstring $(OS),$#))
$(test_lib_32)
else
$(test_lib_64)
endif
$(TARGET_DIR) variable is passed as parameter to make command
make TARGET_DIR=$(TGT32) all32
For 64-bit we will pass TARGET_DIR=$(TGT64) instead.
Note: test_lib_32/64 above are macro definition in other make file which we are including in current make file.
It works fine as expected, But I am not sure whether this is the best way? And I notice one problem here, Generally TGT32/TGT64 variable which we are passing has values either:
/test/scratch/<client_name>/Linux/ (for 32bit)
or
/test/scratch/<client_name>/Linux-64bit/... (for 64bit)
If someone creates a client with client_name 'Linux' string it wont work. It will always go to if block and try to run 32-bit command even when we run 64-bit build. How can I handle this?
I am looking for getting the string after 'client_name' in above path.. Could please help?

If your target directory name is always something like:
/test/scratch/<client_name>/Linux/...
and there is no / character and no spaces in <client-name>, you can base your test on the exact position of Linux (or Linux-64bit) in the path:
TYPE := $(word 4,$(subst /, ,$(TARGET_DIR)))
ifeq ($(TYPE),Linux)
<something>
else ifeq ($(TYPE),Linux-64bit)
<something-else>
else
$(error Unknown type: $(TYPE))
endif
EDIT if the position is not constant but you know what <client-name> is, and there is no / character and no spaces in <client-name>, you can extract the name of the directory that follows <client-name> in $(TARGET_DIR) like this:
TAIL := $(subst /, ,$(TARGET_DIR))
$(foreach w,$(TAIL),$(eval TAIL := $(wordlist 2,$(words $(TAIL)),$(TAIL)))$(if $(patsubst $(CLIENT_NAME),,$(w)),,$(eval TYPE := $(word 1,$(TAIL)))))
all:
#printf 'TYPE = $(TYPE)\n'
Demo:
$ make TARGET_DIR=/a/b/test-client/Linux/c/d CLIENT_NAME=test-client
TYPE = Linux
$ make TARGET_DIR=/a/b/c/d/test-client/Linux-64bit/e/f/g CLIENT_NAME=test-client
TYPE = Linux-64bit
Explanation:
TAIL := $(subst /, ,$(TARGET_DIR)) replaces the / characters in $(TARGET_DIR) by one space, such that it becomes a list of words (one per directory in the path), and assigns the result to variable TAIL.
$(foreach w,$(TAIL),...) evaluates ... with variable w set successively to each word of $(TAIL).
$(eval TAIL := $(wordlist 2,$(words $(TAIL)),$(TAIL))) re-assigns variable TAIL by removing the leading word.
$(if $(patsubst $(CLIENT_NAME),,$(w)),,...) evaluates ... if $(w) equals $(CLIENT_NAME).
$(eval TYPE := $(word 1,$(TAIL))) assigns the first word of $(TAIL) to variable TYPE.
There may be simpler ways to do the same...

If I understand your question correctly then you get problems with the following $(TARGET_DIR) value:
/.../Linux/.../Linux-64bit
The problem is that you are testing on the value of $#:
/.../Linux/.../Linux-64bit/some/other/file
As you can see that makes it impossible to decide which Linux in the path should be used for the decision.
I would suggest to try this instead:
# if $(TARGET_DIR) ends with /Linux -> 32-bit build
$(RELEASE_FILES): $(TGTDIRFILES)/%: %
ifneq ($(filter %/Linux,$(TARGET_DIR)),)
$(test_lib_32)
else
$(test_lib_64)
endif

Related

How to print dependencies list using single variable?

I have created a make file with list of variables. I have added all the files list and assigned to a variable in that make file.
ROLS= \
$(wildcard *.c)
The files list is more than 10000. when I try to print this variable using below rule , it is not printing all the files list.
all:
#echo $(ROLS)
How to print entire list of "c" files in a single variable.
I am using windows 7 64bit system to run this makefile.
Windows has a limit on the complete size of a command line that it will accept. There's nothing make can do about that, it's a hard limit by the operating system.
If all you want to do is actually print the content, then you can use make's internal info function instead so it doesn't try to invoke a Windows command:
all:
$(info $(ROLS))
Of course that won't help if you're trying to do something else with those files that requires a command to be invoked.
Based on additional information in comments:
ROLS= $(wildcard *.c)
target: $(ROLS)
build-target
Or if ROLS used more than once, some efficiency can be achieved with phony target
ROLS= $(wildcard *.c)
.PHONY: all-rols
all-rols: $(ROLS)
target1: all-rols
build-target1
target2: all-rols
build-target2
target3: all-rols
build-target3

Make: Variable assignment

I am facing the issue while accessing a variable from other makefile which is included.
i have test.mak which has variable LIBS32 := $(TESTLIBS)/$(NEW_PLAT32)
i have included test.mak in other makefile and trying to assign that variable in one of the target.
extlib32: EXTLIBS = $(LIBS32)
extlib64: EXTLIBS = $(LIBS64)
The expected value of EXTLIBS should be '/home/testlib/extlibs/Linux' . But here when i print EXTLIBS the value which i am seeing is '/home/testlib/extlibs/'
Note:- When i jut print LIBS i can see the content as expected. But when i assigned to EXTLIBS and try to use it.. I can see word 'Linux' is missing.
Thanks!
You set EXTLIBS as a target-specific variable for target extlib32. Such variables are non-global and their value is only available in the target recipe and target's prerequisites, rather than globally (this is why $(info $(EXTLIBS)) doesn't print the expected value).
To print its value you need to print it from the recipe of target extlib32, e.g.:
extlib32:
#echo "EXTLIBS=${EXTLIBS}"
If extlib32 and extlib64 are .PHONY targets to build something then your original target-specific assignments should propagate to the dependencies and be available in their recipes. You just cannot print its value from the global makefile scope.
To have one makefile build both in 32 and 64-bit mode (as well as release and debug) you need to structure it differently and invoke make separately for each build mode. Example:
# User can overrided capitalized variables. E.g.
# make BUILD=release MODE=32 LDLIBS=-lrt
BUILD := debug
MODE := 64
build_dir := ${BUILD}/${MODE}
ldlibs.32 := my-32-bit-libs
ldlibs.64 := my-64-bit-libs
ldlibs := ${ldlibs.${MODE}} ${LDLIBS}
all :
#echo "build_dir=${build_dir}"
#echo "ldlibs=${ldlibs}"
Usage examples:
$ make
build_dir=debug/64
ldlibs=my-64-bit-libs
$ make BUILD=release MODE=32
build_dir=release/32
ldlibs=my-32-bit-libs
Another example for debug/release compiler options.

Portable way to check if a path is absolute or relative in a makefile

I am working on a new Makefile insfrastructure, which I've managed so far to make very portable in the sense that it works (with GNU-make):
on Linux
on Windows (only with CMD shell + GnuWin32 CoreUtils + GnuWin32 Make)
on Windows (with MSYS2 shell)
I am using canned recipes, and the recipes use arguments provided from the top. These arguments are typically header include dirs and library include dirs.
So far I've assumed that all the paths provided to the canned recipes are relative to where the Makefile resides - Within the recipes, these paths always prefixed with:
ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
Very recently, I've come across an example where the include path that I need to specify is not simple to specify in relative terms.
If coreutils "realpath --relative-to" was working on Windows I would use it, but it isn't. Also on Windows, relative paths are not always possible anyway e.g. if the include dirs and the makefile are on different drives.
So my preferred approach at the moment would be to detect in the canned recipe if the path provided as argument is absolute or relative. Only if it's relative, it gets prefixed with ROOT_DIR otherwise it's used as is.
Any suggestions how to do this, in a robust and portable way ?
We could check various cases:
leading /,
leading ~,
leading X: where X is an upper case letter (Windows drive),
leading \\ (Windows network drive)
and for each set a variable to yes or the empty string:
IS_ROOT := $(if $(patsubst /%,,$(THE_PATH_TO_CHECK)),,yes)
IS_HOME := $(if $(patsubst ~%,,$(THE_PATH_TO_CHECK)),,yes)
IS_NETWORK := $(if $(patsubst \\\\%,,$(THE_PATH_TO_CHECK)),,yes)
IS_DRIVE := $(foreach d,A B C D E...Z,$(if $(patsubst $(d):/%,,$(THE_PATH_TO_CHECK)),,yes))
Then, we can test if the concatenation of these variables equals yes or not:
ifeq ($(strip $(IS_ROOT)$(IS_HOME)$(IS_NETWORK)$(IS_DRIVE)),yes)
<absolute>
else
<relative>
endif
Of course, if you have other cases in mind you can add them using similar combinations of make functions.

Make 3.82 - Backward incompatibility issue?

I'm having some issues after try to run some small Makefile with make 3.82.
error:
[me#localhost make]$ make
Makefile:3: *** empty variable name. Stop.
This works with make 3.81, but not with the new one. I know there are some backward compatibilities with the old version.
I have two Makefiles, a base one and main one.
This is my Makebase
define TestFile
ifeq ($$(shell test $(1) $(2) || echo 1),1)
$$(error $(2) mmm, not found)
endif
endef
define CheckIt
$(eval $(call TestFile,-d,$(1)))
endef
define CheckDir
p := $(foreach d,$1,$(call CheckIt,$d))
endef
define SomeCheck
$(call CheckDir,$(1))
endef
This is my Makefile
include Makebase
$(call SomeCheck, ~/test/make)
As I said, it works fine in make 3.81.
Any help will be appreciated.
Thanks
BR
So, I have no idea what this was intended to do in GNU make 3.81. As Etan points out, when I run your makefile with GNU make 3.81 I get this error:
make: *** No rule to make target `=', needed by `p'. Stop.
That's because a call function cannot expand to a variable assignment, so make interprets the p := as if it were p: = (that is, a target p with a prerequisite of =). I don't see how this is actually what you want. If you don't see this error all I can assume is that somewhere in your makefile, someone has declared a recipe with target = (ugh!!)
In GNU make 3.82 I see the empty variable name message. The reason for this is that GNU make 3.82 introduced parser enhancements which caused some backwards-incompatibility. The NEWS file gives this warning:
As a result of parser enhancements, three backward-compatibility issues
exist: first, a prerequisite containing an "=" cannot be escaped with a
backslash any longer. You must create a variable containing an "=" and
use that variable in the prerequisite.
An unnoticed side-effect of this is that an equals sign with no value before it in the prerequisites list is now considered a target-specific variable where the variable name is empty, whereas before it was assumed to be a target since it didn't meet the requirements for a variable assignment. I am not sure this is a bug... in general I'm not a fan of "tricking" the parser with odd corner cases so I actually prefer the newer behavior.
This entire define is quite bogus:
define CheckDir
p := $(foreach d,$1,$(call CheckIt,$d))
endef
Why? Because the CheckIt user-defined function contains nothing but an eval statement. But eval statements are expanded and the results parsed by make, so they always expand to the empty string. Therefore, the entire foreach loop expands to the empty string. Therefore even if this were interpreted as you (apparently) intended by make, it would always simply expand to:
p :=
which doesn't seem very useful. If you change the above define to simply:
define CheckDir
$(foreach d,$1,$(call CheckIt,$d))
endef
then it will always work, and you won't see these weird problems.
I'm not going to comment on how bogus this makefile is in general... :)

Common GNU makefile directory path

I'm trying to consolidate some build information by using a common makefile. My problem is that I want to use that makefile from different subdirectory levels, which makes the working directory value (pwd) unpredictable. For example:
# Makefile.common
TOP := $(shell pwd)
COMPONENT_DIR := $(TOP)/component
COMPONENT_INC := $(COMPONENT_DIR)/include
COMPONENT_LIB := $(COMPONENT_DIR)/libcomponent.a
If I include Makefile.common from a subdirectory, like so, the $(TOP) directory is incorrect and everything else follows suit:
# other_component/Makefile
include ../Makefile.common
# $(COMPONENT_LIB) is incorrectly other_component/component
What's the best way to get Makefile.common to use its own directory path instead of the more fickle pwd?
You should be able to use the MAKEFILE_LIST variable, like this:
# This must be the first line in Makefile.common
TOP := $(dir $(firstword $(MAKEFILE_LIST)))
From the documentation:
As make reads various makefiles, including any obtained from the MAKEFILES variable, the command line, the default files, or from include directives, their names will be automatically appended to the MAKEFILE_LIST variable. They are added right before make begins to parse them. This means that if the first thing a makefile does is examine the last word in this variable, it will be the name of the current makefile. Once the current makefile has used include, however, the last word will be the just-included makefile.
Try this:
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
Edit: Be sure to use := instead of = because the latter causes make to use late-binding and MAKEFILE_LIST may have changed due to later includes.
Have you tried doing:
# Makefile.common
TOP ?= $(shell pwd)
COMPONENT_DIR := $(TOP)/component
COMPONENT_INC := $(COMPONENT_DIR)/include
COMPONENT_LIB := $(COMPONENT_DIR)/libcomponent.a
# other_component/Makefile
TOP ?= ..
include ../Makefile.common
Using the ?= construct will keep TOP from being redefined if it is already set. You can set it to the appropriate value based on where you are in the tree when you invoke make. I confess it's been awhile since I've used GNU make so this may not work or may need some tweaks.
My solution:
cwd := $(shell readlink -en $(dir $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))))
This also works for calls like make -f /opt/some/dir/Makefile whenn your in /opt/other/path/subdir.
write the common stuff in common.mk. Then put the common.mk in the default directories that Make looks for when it encounters an include statement. See the manual for common directories Make looks for.
You could also put the common.mk in custom directory, and then type make -I customdir.
Inside the Makefile in each subfolder, you do
include common.mk
That is all. No need to worry about path and moving things around.

Resources