Common GNU makefile directory path - makefile

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.

Related

How can I add a directory to the search path of GNU Make?

I have a makefile that looks something like this:
include anotherFile.mk
all:
someStuff
The file anotherFile.mk is like this:
include yetAnotherFile.mk
export SOME_VAR = 93
The problem is that anotherFile.mk and yetAnotherFile.mk are in a different directory from my Makefile. So my makefile can't just be changed to this:
include $(OTHER_PROJECT_PATH)/anotherFile.mk
all:
someStuff
The problem with this approach is that the include statement in anotherFile.mk will fail because it will be searching in the current directory.
A partial solution that I found is to pass the --include-dir=$OTHER_PROJECT_PATH flag to the invocation of make, but that's a bit user-unfriendly.
So my question is: Is there something I can put inside my makefile that will add to the directories that make searches for when executing an include? Something like MAKE_INCLUDE_DIRS += $(OTHER_PROJECT_PATH)
Surprisingly there doesn't seem to be a good answer to that question. Forcing .INCLUDE_DIR doesn't help and there doesn't seem to be any way around invoking make with --include-dir=$OTHER_PROJECT_PATH.
It is however possible to put the appropriate recursive make invocation inside the makefile but, in order to get it to work for all reasonable cases it quickly becomes too complicated to be worth it. In summary it requires:
a top level condition to check if the OTHER_PROJECT_PATH is in .INCLUDE_DIR
the appropriate target with the recipe invoking make recursively
possibly additional targets if there are multiple command goals
the real make file enclosed in the else part of the conditional
You Makefile would look like this:
OTHER_PROJECT_PATH := other
ifeq (,$(filter $(OTHER_PROJECT_PATH), $(.INCLUDE_DIRS)))
# this is the mechanism to add the include dir in a recursive make
$(or $(firstword $(MAKECMDGOALS)),all):
$(MAKE) -I$(OTHER_PROJECT_PATH) $(MAKECMDGOALS)
# add empty targets for additional goals if needed
ifneq (,$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)))
$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)):
endif
else
# this is where the real makefile starts
all more:
echo $#: $< $^
include a.mak
endif
It still does not seem possible from a makefile, but if you have a script that sets up environment variables, you can use MAKEFLAGS (e.g. export MAKEFLAGS=I/your/path ordentlich on Linux, or SET on Windows)

Getting parent directory's basename in makefile

I need to automate a variable alignment in my Makefile. My Makefile's full file path is:
/home/e2/branchname/projectname/modulename/Makefile
In my Makefile, I have a variable BUILD_DIR, a part of which should be equal to the branchname in the full path.
So I did this:
BRANCH_NAME= $(shell cd ../.. && basename "$PWD" && cd projectname/modulename)
BUILD_DIR=$(HOME)/$(BRANCH_NAME)/build
Apparently I expected BUILD_DIR to be ~/branchname/build here, but after make I got ~/WD/build instead. I think it's most likely that I got a wrong BRANCH_NAME. Is there something wrong with what I did? And if yes I'd like to get some advice about how to do it correctly.
Thanks.
It's because $ has a special meaning to Make, so if you want to pass that up to shell you have to "escape" it. In case of Make, you escape the dollar sign by doubling. So you have to use $$PWD.
Also, what you are doing is not really the best way - it is always best to avoid the shell and use Make functionality if possible. In your case, the best way to do what you want is this:
BUILD_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../../build)
You have to put the above line in the makefile in question, near the top, so that it is before you include any other makefiles.
I came up with this:
ENVIRONMENT := $(shell basename $(dir $(abspath $(dir $$PWD))))
If this were executed, you'd have:
ENVIRONMENT=projectname

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)

Obtaining directory makefile resides in

What is the correct way to get the directory where the currently executing makefile resides?
I'm currently using export ROOT=$(realpath $(dir $(lastword $(MAKEFILE_LIST)))) and am running into some problems where when running make with the exact same options will result in different values for ROOT. About 90%of the time it has the correct value, but in the remaining 10% there are a number of invalid paths.
realpath,abspath,lastword and a couple of more functions were only introduced in GNU Make 3.81 [See ref]. Now you can get the current filename in older versions using words and word:
THIS_MAKEFILE:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
But I am not sure of a workaround for realpath without going to the shell. e.g. this works with make v3.80:
THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd)
THIS_MAKEFILE:=$(notdir $(THIS_MAKEFILE_PATH))
all:
#echo "This makefile is $(THIS_MAKEFILE) in the $(THIS_DIR) directory"
Which gives
$ make -f ../M
This makefile is M in the /home/sandipb directory
Ref: http://cvs.savannah.gnu.org/viewvc/make/NEWS?revision=2.93&root=make&view=markup
$(shell pwd) is not correct since the makefile might exist in a directory other than pwd (as allowed by make -f).
The OP's proposed
export ROOT=$(realpath $(dir $(lastword $(MAKEFILE_LIST))))
is fine, except, s/he probably wants to use firstword instead, especially if the top level makefile (potentially) includes other makefile(s) prior to assiging to ROOT.
The OPs 10% problem could be explained if there was a conditional include 10% of the time prior to the assignment, but, hey, that's a guess...
For your convenience, when GNU make starts (after it has processed any -C options)
it sets the variable CURDIR to the pathname of the current working directory. This value
is never touched by make again: in particular note that if you include files from other
directories the value of CURDIR does not change. The value has the same precedence it
would have if it were set in the makefile (by default, an environment variable CURDIR will
not override this value). Note that setting this variable has no impact on the operation of
make (it does not cause make to change its working directory, for example).
all:
echo $(CURDIR)

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