Hi David, Ingo,

For others starting to read this thread now, it started here:
<https://lists.gnu.org/archive/html/bug-make/2022-08/msg00059.html>

On 8/22/22 16:46, David A. Wheeler wrote:


On Aug 22, 2022, at 12:02 AM, Alejandro Colomar <alx.manpa...@gmail.com> wrote:

Hi David,

On 8/22/22 04:45, David A. Wheeler wrote:
On Aug 20, 2022, at 11:35 AM, Alejandro Colomar <alx.manpa...@gmail.com> wrote:
I'd say there is:  make(1) treats file names as text strings, not really file 
names, for most of its operations.  As an example, foo/ and foo/. are different 
targets.  I don't see why ./bar and bar should be the same.  Consistency is 
essential; otherwise, what to expect?  Why does make(1) need to special-case a 
leading ./ ?
Because treating "./foo" and "foo" as different files is likely to lead to 
endless footguns.
Consistency is nice, but making something easy to use *correctly* is more important. I have no 
problem with special-casing "./XXX" as a synonym for "XXX"; anything else would 
be *unexpected* (if for no other reason than backwards compatibility).
As far as "foo/" vs. "foo/.", it's *much* less common to have directories as 
prerequisites or targets, so not handling them with special conveniences seems quite reasonable.

Is it so *much* less common?  I never really knew that make(1) treats ./foo and 
foo as the same thing until this bug report.  make(1) is (almost) very 
consistent in that it handles text, and not really file names.  But _all_ 
non-phony targets should declare a dependency like '|$$(@D)/', unless you can 
guarantee that it already exists by the time you'll try to create the file 
(e.g., when the file depends on another file in the same dir).

My Makefiles are plagued with that dependency, but a correctly-written Makefile 
shouldn't even notice that ./ is trimmed.

I also try to do that, but I think it'd backwards-incompatible if GNU make 
changed its current behavior.
I suspect a lot of makefiles would quietly work incorrectly if that change was 
made.

Yeah, I guess the bug is resolving ./, and also that we will live forever with that bug.


Do other make(1) implementations trim this leading ./?

I tried on OpenBSD, and it seems to do that and more; it seems to be resolving symlinks and .. (physically, although I'm not yet sure of what it does to .. after segments of the path that don't exist). It doesn't seem to be documented, and also doesn't seem to be obvious in the source code (I can't find any realpath(3) calls, nor '..').

I wrote a program that behaves similar to how I think BSD make(1) behaves, after testing it a little bit:

$ cat canonicalizepath.c
#include <bsd/string.h>
#include <err.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


char *canonicalizepath(const char *path);


int
main(int argc, char *argv[])
{
        int   ret;
        char *p;

        if (argc < 2)
                errx(EXIT_FAILURE, "missing operand");

        ret = EXIT_SUCCESS;

        for (ptrdiff_t i = 1; i < argc; i++) {
                p = canonicalizepath(argv[i]);
                if (!p) {
                        ret = EXIT_FAILURE;
                        warn("%s", argv[1]);
                } else {
                        puts(p);
                        free(p);
                }
        }

        exit(ret);
}


char *
canonicalizepath(const char *path)
{
        char *cpy, *end, *sep, *p, *ret;
        size_t len;

        cpy = strdup(path);
        if (!cpy)
                return NULL;

        end = cpy + strlen(cpy);
        sep = end;

        do {
                *sep = '\0';

                p = realpath(cpy, NULL);
                if (p)
                        goto found;

                sep = strrchr(cpy, '/');
        } while (sep);

        sep = cpy;
found:
        len = end - sep + 1;
        if (p)
                len += strlen(p);

        ret = malloc(len);
        if (!ret)
                goto fail;

        ret[0] = '\0';

        if (p)
                strlcat(ret, p, len);
        for (char *q = sep; q < end; q++) {
                if (*q == '\0')
                        *q = '/';
        }
        strlcat(ret, sep, len);

fail:
        free(cpy);
        return ret;
}


alx@asus5775:~/tmp$ ./canonicalizepath .
/home/alx/tmp
alx@asus5775:~/tmp$ ./canonicalizepath ./foo/../asd
/home/alx/tmp/foo/../asd
alx@asus5775:~/tmp$ ./canonicalizepath ./asd
/home/alx/tmp/asd
alx@asus5775:~/tmp$ ./canonicalizepath asd
asd
alx@asus5775:~/tmp$ ./canonicalizepath canonicalizepath
/home/alx/tmp/canonicalizepath
alx@asus5775:~/tmp$ ./canonicalizepath /home/../tmp/../../etc/
/etc


I don't think me want GNU make(1) to behave like that.


Ingo, do you know how your make(1) behaves with respect to '..', '//', '.', and symlinks? Is that documented anywhere?



No idea. However, I took a quick look at the POSIX make specification:
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html

The POSIX specification for make is notoriously underpowered.
It doesn't say anything about whether or not ./FOO is the same as FOO.
It refers to files by PRECEDING them with "./", e.g.
"By default, the following files shall be tried in sequence: ./makefile and 
./Makefile. If neither ./makefile or ./Makefile are found, other implementation-defined 
files may also be tried. [XSI]   On XSI-conformant systems, the additional files 
./s.makefile, SCCS/s.makefile, ./s.Makefile, and SCCS/s.Makefile shall also be 
tried."
I interpret that as an expectation that adding "./" is expected to have the same
result if the filename starts with an alphabetic character, but that's
just my reading and not directly supported.

That refers to the input file, and not to targets within the DAG, AFAICS.


Cheers,

Alex


--
Alejandro Colomar
<http://www.alejandro-colomar.es/>

Attachment: OpenPGP_signature
Description: OpenPGP digital signature

Reply via email to