How can I shift a relative path up one level in make? - makefile

Sorry if this is a little esoteric, but in my makefile I have a variable $(BASE) which is a relative path. I need to get the path one level up.
So if I had "../../../src", I want "../../src".
Is there way to do this easily in Make?

If all values of BASE for which you want to do this begin with ../, you can try
$(patsubst ../%,%,$(BASE))
If you want to just drop the first component of an arbitrary path (i.e. a/b/c -> b/c), it takes a bit more work:
space := $(empty) $(empty)
shift-list = $(wordlist 2,$(words $1),$1)
shift-path = $(subst $(space),/,$(call shift-list,$(subst /, ,$1)))
and use it as $(call shift-path,$(BASE)). This breaks if your paths have spaces, but handling those in make is a nightmare for many other reasons anyway.

Related

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

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

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.

How to use patsubst in makefile to do multiple substitutions

I am trying to generate list of object files from source files in my makefile using patsubst
OUT_DIR=Out/
SRC=../../../Client2.4/Client/src/BrokerModule/BrokerApp.cpp
../../../Client2.4/Client/src/CommandMsgManager/CConfigModuleInfo.cpp
OBJ:= $(patsubst %src/%.cpp,${OUT_DIR}$%.o,$(SRC))
I want my OBJ variable to be
OBJ=Out/BrokerModule/BrokerApp.o Out/CommandMsgManager/CConfigModuleInfo.o
after patsubst but above patsubst is not producing the desired result. Please help.
There are some problems with the usage of patsubst, see my suggestion as followed,
OUT_DIR=Out/
SRC=../../../Client2.4/Client/src/BrokerModule/BrokerApp.cpp \
../../../Client2.4/Client/src/CommandMsgManager/CConfigModuleInfo.cpp
# add the definition of src
src=../../../Client2.4/Client/src/
# Modify the definition of OBJ
OBJ:= $(patsubst ${src}%.cpp,${OUT_DIR}%.o,$(SRC))
Filtered out the prepended ${src} and appended .cpp, and keep only
BrokerModule/BrokerApp.cpp & CommandMsgManager/CConfigModuleInfo.cpp.
And % is replaced by the text that matched the % in the previous step.
Patsubst can only handle patterns with one wildcard in it, unluckily. Moreover you are trying to take apart path names not the usual way at the file level. That means, as long as you neither know the prefix nor the postfix parts of the /src/ in your strings, you are out of luck as you can never say 'replace unknown prefix and conserve unknown postfix' (or the other way round).
The usual solution is to 'know' the prefix:
OUT_DIR=Out/
SRC_PATH := ../../../Client2.4/Client/src
SRC=../../../Client2.4/Client/src/BrokerModule/BrokerApp.cpp \
../../../Client2.4/Client/src/CommandMsgManager/CConfigModuleInfo.cpp
OBJ:= $(patsubst $(SRC_PATH)/%,${OUT_DIR}%,$(SRC))
$(info $(OBJ))
Another solution is to use e.g. the GNUmake table toolkit library of make functions (still beta but your problem can be solved):
include gmtt.mk
OUT_DIR=Out
SRC=../../../Client2.4/Client/src/BrokerModule/BrokerApp.cpp \
../../../Client5.6/Client/src/CommandMsgManager/CConfigModuleInfo.cpp
strip-till-last-src = src/$(call implode,$(call down-to,src/,$(call explode,/,$1)))
OBJ:= $(foreach a-path,$(SRC),$(OUT_DIR)/$(call strip-till-last-src,$(a-path)))
$(info $(OBJ))

Get makefile directory

I am distributing my cpp files along with a makefile. Now the makefile is located in the same directory as the cpp file.
What is the variable (if any) in makefile that allows me to retrieve the current directory where the makefile is located? In this way I can use that variable to specify my cpp path for compilation.
My makefile is as follows:
all:
g++ ($makeFileDir)/main.cpp ($makeFileDir)/hello.cpp ($makeFileDir)/factorial.cpp -o ($makeFileDir)/hello.exe
Edit: I am running my makefiles on Windows
I remember I had the exact same problem. It's not possible, as far as I remember.
The best bet you can have is to pass it as a variable. That is both cross platform and guaranteed to work, as you know the makefile dir at invoke time (otherwise you can't invoke it).
In alternative, you can do a very dirty trick, meaning you try to combine your current path (you can obtain with $(CURDIR) in gnu make) with the path of the invocation of the makefile (which can be tricky, and depends on your make)
Here is a cross-platform way to get the directory of the Makefile, which should be fully shell-agnostic.
makeFileDir := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
Note that this will give you the directory of the Makefile being currently interpreted. You might have bad (or good!) surprises if you include a Malefile using this statement from another.
That should be enough if you use a recent implementation of make for windows, i.e. Chocolatey's.
Issues with older make for Windows
Depending on the version of make you're using on Windows, there can be inconsistencies in the handling of backslashes. You might need one of the following variant. That's the case for GnuWin's make 3.81 binary for example.
Make the path separator consistent. The statement below uses forward slashes only, just swap \ and / to get the opposite behavior. From my experience (with GnuWin's make), you might have to use forward slashes to use such a variable for make include statements or to use it in VPATH.
But you would of course need backslashes in the DOS shell, and therefore in recipes... You might need two versions of the variable, but at least the substitution makes sure that the path separator is consistent!
makeFileDir := $(subst \,/,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
The abspath function of GnuWin make 3.81 is broken and doesn't handle paths with drive letters in it. Here is a workaround to handle Windows absolute paths (with drive letter) as well. You can then use it to get the directory of the Makefile (here with the path separator substitution as well).
I won't explain the details, but the workaround simply returns the argument if that's already a Windows absolute path, i.e. if there is : in the root of the path, and uses the builtin abspath otherwise.
define fixabspath
$(if $(findstring :,$(firstword $(subst /, ,$(subst \,/,$(1))))),$\$
$(1),$\
$(abspath $(1)))
makeFileDir := $(subst \,/,$(dir $(call fixabspath,$(lastword $(MAKEFILE_LIST)))))
Remarks
There might be sources I'm omitting here and I'm sorry for that. It's been a long time ago.
In the fixabspath definition, $\ are just here to split the line for readability.
The MAKEFILE_LIST variable contains a list of the Makefiles being interpreted, the last one being the current one. See the corresponding manual page.
If I remember correctly, this also works with macOS' native make.
For 'cygwin' and 'linux' use I've solves this by calling pwd directly from the rule in the makefile:
do.%: %.cpp
echo "Running command in " `pwd`
somecommand $^
you can use $(srcdir)
then ./configure --srcdir="/your/path/to/the/source/directory"

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