Add suffix to string only if not already present - makefile

In a (GNU) Makefile, I want to add an optional DESTDIR prefix to a path (which may or may not be absolute), as in:
The original path is ${DIR} (always defined and non-empty);
The DESTDIR variable specified by the user may be empty, and it may or may not terminate with a slash.
I want in all cases the resulting ${DESTDIR} + ${DIR} concatenation to be a valid path and without double slashes. That is:
if DESTDIR is empty or ends with a slash, just do ${DESTDIR}${DIR};
otherwise, do ${DESTDIR}/${DIR}.
The following does seem to work (at least on a few tests):
ifeq (${DESTDIR},)
# DESTDIR is empty
FULLDIR:=${DIR}
else
ifeq ($(patsubst %/,$,${DESTDIR}),${DESTDIR})
# DESTDIR has no trailing slash
FULLDIR:=${DESTDIR}/${DIR}
else
# DESTDIR has a trailing slash
FULLDIR:=${DESTDIR}${DIR}
endif
endif
$(info FULLDIR: ${FULLDIR})
But it is very cumbersome. Is there a simpler/shorter way to obtain the same results?

GNU Make has some built-in functions to manipulate file names.
Ones in your interest might be $(abspath *files...*) and $(realpath *files...*).
Both functions will take your path and convert it to canonical absolute path, which is absolute path without repeating slash, .. and .. Only difference is that realpath will resolve symlinks but abspath will not.
Though if you want something not absolute path, only thing I can tell is there's no easy way for it. One may try creating regex that replaces repeating slash into one and use $(shell) function with regex command line utility, but it will still contain a lot of ambiguity like having .. or three slashes in a row.
So my recommendation is to do $(realpath ${DESTDIR}/${DIR}).

FULLDIR := $(if ${DESTDIR},$(patsubst %/,%,${DESTDIR})/${DIR},${DIR})

Related

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.

Extracting part of a match in a makefile rule

I have a makefile which generates a bunch of versions of an image in different places:
website/img/logo_256.png
website/img/logo_152.png
/tmp/logo_64.png
and so on (the /tmp/ generation is so I can later use those files to later generate a multiresolution .ico, the details of that aren't important).
I'd like a rule of the form
logo_%.png: ${SRC}
convert $^ -thumbnail $*x$* $#
but, $* brings in the matched directory too, so I get a command of the form:
convert logo_1024.png -thumbnail /tmp/64x/tmp/64 /tmp/logo_64.png
which is incorrect (I need 48x48, not /tmp/48x/tmp/48).
Or I can write
/tmp/logo_%.png: ${SRC}
convert $^ -thumbnail $*x$* $#
website/img/logo_%.png: ${SRC}
convert $^ -thumbnail $*x$* $#
which seems ugly.
I'm sure there are ways to break down and pattern match $# to get what I want, but I'm not a makefile guru, so this would take some research.
What's the easiest way to do this?
See the second half of the Automatic Variables in the GNU Make Manual:
Of the variables listed above, four have values that are single file names, and three have values that are lists of file names. These seven have variants that get just the file's directory name or just the file name within the directory. The variant variables' names are formed by appending ‘D’ or ‘F’, respectively. These variants are semi-obsolete in GNU make since the functions dir and notdir can be used to get a similar effect (see Functions for File Names). Note, however, that the ‘D’ variants all omit the trailing slash which always appears in the output of the dir function. Here is a table of the variants:
‘$(#D)’
The directory part of the file name of the target, with the trailing slash removed. If the value of ‘$#’ is dir/foo.o then ‘$(#D)’ is dir. This value is . if ‘$#’ does not contain a slash.
‘$(#F)’
The file-within-directory part of the file name of the target. If the value of ‘$#’ is dir/foo.o then ‘$(#F)’ is foo.o. ‘$(#F)’ is equivalent to ‘$(notdir $#)’.
‘$(*D)’
‘$(*F)’
The directory part and the file-within-directory part of the stem; dir and foo in this example.
‘$(%D)’
‘$(%F)’
The directory part and the file-within-directory part of the target archive member name. This makes sense only for archive member targets of the form archive(member) and is useful only when member may contain a directory name. (See Archive Members as Targets.)
‘$(<D)’
‘$(<F)’
The directory part and the file-within-directory part of the first prerequisite.
‘$(^D)’
‘$(^F)’
Lists of the directory parts and the file-within-directory parts of all prerequisites.
‘$(+D)’
‘$(+F)’
Lists of the directory parts and the file-within-directory parts of all prerequisites, including multiple instances of duplicated prerequisites.
‘$(?D)’
‘$(?F)’
Lists of the directory parts and the file-within-directory parts of all prerequisites that are newer than the target.
Edit:
As prompted by #Ian's comment I looked again and realized that this was not a complete solution. A complete solution follows.
The above F modifiers (and the $(notdir) function) will strip the path from the target filename. That's part of what is necessary.
Additional manipulation is required to extract only the numerical component from target like /some/path/logo_64.png.
The $(basename) function will strip off suffixes (as will $(patsubst %.png,%,$#) or $(#:%.png=) in a more specific fashion).
Combining those we get from /some/path/logo_64.png to logo_64. Handling things at this point depends heavily on what the data is going to look like and what assertions about it can be made. If logo_ is a static prefix then a simple $(patsubst logo_%,%,...) will work (as will the matching substitution reference like before).
If that is not guaranteed but the guarantee can be made that the dimension will be the last underscore separated component then $(lastword $(subst _, ,...)) can be used.
The rule needed is:
logo_%.png: ${SRC}
convert $^ -thumbnail $(*F)x$(*F) $#
The $(*F), is documented very briefly in the Make manual, as quoted in Etan's answer.
‘$(*F)’ The file-within-directory part of the stem; foo in this example.
The 'stem' ($*) is anything not explicit in the pattern. That includes the wildcard, and any implicit directories. So hence in the question it had the value /tmp/48, /tmp/ from the implicit directory, and 48 from the wildcard in the pattern. So of this combined stem, I need to select just the filename part, $(*F).
Alternatively, noting that the manual states:
These variants are semi-obsolete in GNU make since the functions dir and notdir can be used to get a similar effect
we can instead do:
logo_%.png: ${SRC}
convert $^ -thumbnail $(notdir $*)x$(notdir $*) $#
In a comment, Etan also linked to the How Patterns Match section of the manual, to help understand how the stem is constructed. I found this useful and wanted to bubble it up into an answer.

How to get the basename of the current folder as variable in a makefile?

I want to copy a file to a server using scp. But I want to use my current folder name in my makefile as variable.
I know I get my current path using $(CURDIR) but my local path isn't the same on my remote server.
E.g. my path is /Users/obstschale/Documents/Lab2/ and I want to copy Lab2.tar to user#server.au:/home/path/Lab2/.
copy2server:
echo $(CURDIR)
scp Lab2.tar user#server.au:/home/path/{folder}
I probably have to pipe $(CURDIR) into something and find my last folder.
Update: $(CURDIR) is the right variable. $(CURID) is the wrong one at least it didn't work for me.
I didn't have luck with the backtick syntax in makefiles (GNU Make 3.81) as Sylvain describes it. If it doesn't work for you either, use
$(shell basename $(CURDIR))
instead of
`basename $(CURDIR)`
I tried this rule:
test:
#echo $(CURDIR) # e.g. /tmp/foobar/blafoor/baz
#echo $(notdir $(CURDIR)) # gives "baz" here.
which worked fine for me.
maybe this is not intended to work, because notdir should
Extract the non-directory part of each file name.
If you're looking to strip the last folder name in the path out, there are a number of built in makefile functions. See http://www.chemie.fu-berlin.de/chemnet/use/info/make/make_8.html#SEC74 for a quick overview of file operations.
copytoserver:
scp Lab2.tar user#server.au:/home/path/$(notdir $(CURDIR))
The key item of this is of course $(notdir $(CURDIR)). As you discovered, the $(CURDIR) contains the path to the directory in which the makefile was run, without the final '/'. The notdir function strips off everything up to and including the last '/' in a filename. This ends up stripping off everything but the final directory.
If $(CURDIR) still has the trailing '/', you can strip that off so the notdir function will do what you want like so:
$(notdir $(patsubst %/,%,$(CURDIR)))
Note that spacing in this case is critically important, placing a space before or after any of the commas will insert a space when it does the greedy pattern substitution.
You can use basename shell command to extract the last component part of $(CURDIR).
copyserver:
folder=`basename "$(CURDIR)"`; scp Lab2.tar user#server.au:/home/path/${folder}
As of GNU Make 4.3, you can use the native basename Make function, like so:
CURRENT_DIRECTORY := $(basename $(CURDIR))
$(info current directory is: $(CURRENT_DIRECTORY))
For more information about available functions, see the GNU Make info manual: info "(make) File Name Functions".

Make include in makefiles be relative to the file's location

Directly related to this question. How can I make the include directive in makefiles behave relatively to the location of the current script?
Assume that the current path is arbitrary and you have no control over it. Only the makefile location is known. Your makefile is not the root one - it's included. That's exactly how it is in Android NDK.
Is there a builtin variable with the current makefile's name? Can I strip filename away from it, leaving just the path? Using make 3.81 on Cygwin.
You can get the name of the makefile being currently processed from MAKEFILE_LIST builtin variable.
Given that the current makefile is the last one that has been included (in other words you didn't use another include directive since the beginning of the current script), the path to the script itself would be:
SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
Now you are able to include a script in the same directory as such (note an absence of slash, it has already been added by $(dir ...)):
include $(SELF_DIR)another.mk
Note: In GNU Make 3.80 there was no lastword builtin function. In that case you may implement it as follows replacing $(lastword ...) with $(call lastword,...):
lastword = $(if $(firstword $1),$(word $(words $1),$1))
Is there a builtin variable with the current makefile's name?
Yes, there is, use ${CURDIR}. This is the directory where top-level Makefile is located, so you don't need to strip anything from it.
http://www.gnu.org/software/make/manual/make.html#Recursion
I find that relative paths work (GNUMake 3.81), but if they don't for you, try this:
include $(abspath ../whatever)

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"

Resources