How to make GNU/Make stop de-referencing symbolic links to directories
Asked Answered
C

1

14

GNU/Make manual §5.7 states the following:

5.7 Recursive Use of make

Recursive use of make means using make as a command in a makefile. This technique is useful when you want separate makefiles for various subsystems that compose a larger system. For example, suppose you have a subdirectory subdir which has its own makefile, and you would like the containing directory's makefile to run make on the subdirectory. You can do it by writing this:

 subsystem:
         cd subdir && $(MAKE) or, equivalently, this (see Summary of Options):

 subsystem:
         $(MAKE) -C subdir

So, basically it implies that cd subdir && $(MAKE) is the same as $(MAKE) -C subdir.

However, it turns out that it is not really an equivalent. When using -C option, the directory path, if it is a symlink, is always de-referenced, so there is no way to get "logical" (as in pwd -L) directory name. Consider the following example. Having the following Makefile in /tmp/b directory, which is a symlink to /tmp/a/ directory:

foo:
    @echo PWDL=$(shell pwd -L)
    @echo PWDP=$(shell pwd -P)
    @echo CURDIR=$(CURDIR)

Now, let's invoke make differently:

$ pwd
/tmp
$ (cd b && make foo)
PWDL=/tmp/b
PWDP=/tmp/a
CURDIR=/tmp/a
$ make -C b foo
make: Entering directory `/tmp/a'
PWDL=/tmp/a
PWDP=/tmp/a
CURDIR=/tmp/a
make: Leaving directory `/tmp/a'
$ make --directory=b foo
make: Entering directory `/tmp/a'
PWDL=/tmp/a
PWDP=/tmp/a
CURDIR=/tmp/a
make: Leaving directory `/tmp/a'
$ 

As you can see, pwd -P and $(CURDIR) are always showing de-referenced symbolic link. But pwd -L works only when changing directory before running make, which proves that -C option to GNU/Make always makes it de-reference the directory path, for no good reason. I was trying to find any explanation for this behavior in the documentation but couldn't. Neither I can come up with a workaround for this problem without changing directory using cd before running make (or w/o very bad hooks trough LD_PRELOAD).

The question is - did anyone else run into this problem before, have an explanation or a workaround? Thanks!

UPDATE:

Trying to get to the bottom of it, I have downloaded source code for make and did not find any special processing of directories, only calls to chdir. So I wrote a little program to conduct an experiment, and here is what I found.

When you are in a symlinked directory (/tmp/b) and trying to change directory to the same, the operation has no effect and current working directory continues to point to the symbolic link.

When you call chdir () and specify either full or relative path, it de-references it before changing directory. Here is a prove:

$ cat cd.cpp 
#include <stdio.h>
#include <unistd.h>

int main ()
{
    chdir ("/tmp/b/");
    printf ("Current dir: %s\n",
        get_current_dir_name ()); /* Forgive my memory leak. */
}

$ gcc -o test ./cd.cpp 
$ pwd
/tmp/b
$ ./test 
Current dir: /tmp/b
$ cd ../
$ ./b/test 
Current dir: /tmp/a
$ cd /
$ /tmp/b/test 
Current dir: /tmp/a
$ 

So it seems like either libc or Linux is playing tricks on me that I didn't really care about before. And on top of that, bashscd` is working somehow different.

Strangely enough, Linux chdir () man page doesn't mention anything about it, but there is a note on it in Borland Builder (!sic) documentation, here. So I wonder what bash does in this regard.

Cubital answered 5/12, 2011 at 19:8 Comment(6)
This is the behavior I would expect from make, actually. The "logical" directory is purely an invention by the shell, which does extra work to maintain the illusion.Darlleen
@ephemient: I thought it is doing something like that, but strangely, when doing cd and invoking a process that calls getcwd (), getcwd () returns a path to symlink, and not the real directory. I have tried to call chdir () with full path to symlink and cannot get the same effect. I am wondering what exactly is going on.Cubital
bash caches the working directory in $PWD. cd in shell updates $PWD. pwd in shell returns $PWD if it matches the real working directory. When you use cd && $(MAKE), the variable is set for the recursive invocation; when you use $(MAKE) -C, it's not, and pwd falls back to the uncached physical path.Darlleen
@ephemient: And the whole thing works until.... I'm going to sketch up a little answer about this thing. It's a mess. Huh.Cubital
BTW: Glibc's get_current_dir_name does check getenv("PWD"). make and bash just use getcwd, however. And obviously, this is all implementation-specific detail.Darlleen
@ephemient: You are right. There is no way to actually chdir () to a symlink, if only there was chdir () accepting inode... Anyways, I will keep far far away from "symlinks as cwd" concept in the future. Thanks for your help figuring this out!Cubital
C
10

With ephemient's help I was able to figure this one out. So there are few things that are going on:

  1. Linux does not support having current working directory set to a symlink. chdir () always sets it to the real directory.
  2. There is a variable called PWD that represents a current working directory, by convention.
  3. Some of the function/system calls honor PWD variable and some do not.
  4. Bash maintains a "fake" path built using symbolic names and sets PWD variable accordingly.

It explains gmake behavior. When gmake is invoked by sh, PWD is updated before gmake process is executed. Thus, PWD is set to "symbolic" path and functions honoring it continue to work fine. Otherwise, gmake calls chdir (), which changes working directory and sets PWD without sh's tricks. Thus, all functions including those honoring PWD start to return "real" path.

All in all, I'd say that depending on symbolic path name is a bad idea and things can easily fall apart. For example, because getcwd () system call doesn't care about PWD. Invoking cd before running make works as a short term solution though.

Cubital answered 5/12, 2011 at 20:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.