Hello Branden,
i have a hard time understanding what your actual problem is,
but i'll try anyway (even though im not an expert in make(1)).
In case i'm missing your point - which seems possible, because you did
not adhere to normal bug reporting methods, which would be saying:
* what exactly you are doing (which exact commands on which exact system)
* what exactly you expect (regarding behaviour and output)
* what exactly you observe instead (exact behaviour and output)
please clarify your question.
G. Branden Robinson wrote on Wed, Jan 21, 2026 at 02:30:04AM -0600:
> [CCing Warren due to my usual GMail->TUHS interference problems]
>
> ...that's how my Hungarian phrasebook tells me to word it.
>
> Since this issue burns me every time I do a groff release (formerly as
> deputy maintainer, now as a full bird), I wish to risk offending BSD
> partisans by griping about their make.
>
> Apparently BSD make tracks file identity in its dependency graph by
> using...strings.
>
> That's it. Just strings.
Not sure that is entirely accurate (again, i'm not a make(1) expert),
but yes, according to my understanding, string comparison is the
main *basis* for resolving targets. It seems there are refining
aspects in some cases; i don't know the exact details and did not
investigate at this time.
> So "foo" != "./foo" != "./././foo" and so on. To BSD make, these are
> different things, even though by design they must be file specifications
> ("pathnames") _on the file system_, and on every Unix, they _are the
> same_.
>
> So you get spurious build failures when, say, macro contents naming a
> target disagree with target names in rules in ways that don't make sense
> when you know what's going on (outside of make implementation details).
>
> Let me offer some simplified examples.
>
> FOO = ./foo
>
> $(FOO): bar
> frob < bar > $(FOO)
Trying that on OpenBSD-current:
$ make
make: don't know how to make bar (prerequisite of: ./foo)
Stop in /home/schwarze/Test/make/branden_dot
No surprise so far, so let's provide the input file:
$ echo Hello world! > bar
$ make
frob < bar > ./foo
/bin/sh: frob: not found
*** Error 127 in /home/schwarze/Test/make/branden_dot (Makefile:4 './foo')
$ make foo
frob < bar > ./foo
/bin/sh: frob: not found
*** Error 127 in /home/schwarze/Test/make/branden_dot (Makefile:4 './foo')
$ make ./././foo
`./././foo' is up to date.
$ rm -f foo
$ make ./././foo
frob < bar > ./foo
/bin/sh: frob: not found
*** Error 127 in /home/schwarze/Test/make/branden_dot (Makefile:4 './foo')
Except that frob(1) is not a real command, your example works as
expected for me. In particular, the ./foo: rule is found even when i ask
for "foo" or "./././foo".
What exactly am i supposed to type to reproduce your problem, and how
exactly does your problem present?
> If the "./" prefix seems like a silly thing to do, consider that it may
> itself be the result of macro expansion.
>
> FOO = $(BAZ)/foo
>
> all: $(FOO)
>
> $(FOO): bar
> frob < bar > $(FOO)
>
> $ make BAZ=/look/over/there
$ rm -f bar foo
$ make BAZ=/look/over/there
make: don't know how to make bar (prerequisite of: /look/over/there/foo)
Stop in /home/schwarze/Test/make/branden_dot
$ echo Hello world! > bar
$ make BAZ=/look/over/there
frob < bar > /look/over/there/foo
/bin/sh: cannot create /look/over/there/foo: No such file or directory
*** Error 1 in /home/schwarze/Test/make/branden_dot
(Makefile:6 '/look/over/there/foo')
Again, except that there is nothing "over/there", the command you propose
works as expected for me.
What exactly do you think is wrong make running the command
frob < bar > /look/over/there/foo
and what do you want make(1) to do instead?
> (Yes, a real Makefile for BSD make would probably not be written this
> way.[1])
If you somehow make sure that BAZ is not empty, it even might be OK,
but that discussion seems unrelated.
> This design is a landmine with a tripwire in front of it because in the
> shell command "recipes" of a makefile rule, you once again get the
> semantics you expect as a Unix file system user, because the _shell_
> resolves whatever $(FOO) expands to. So it is not fooled by "./" or
> "./././." or any other variation on that theme.
>
> I find this design choice even more confusing because Unix has had hard
> links since day one, or just about. The _name_ of a file as a string or
> even a directory entry is not its uniquely identifying characteristic
> (ignoring content).
>
> Why does the BSD make dependency graph not track the device and inode
> pair instead?
I'm sorry, but i'm not following. The point of a dependency rule
is to provide a recipe for how to create a target that does not yet
exist. What exactly do you suggest make(1) should do? Identity a
target by its inode number? A target that does not yet exist does
not have an inode number because it does not exist. A target that
already exists no longer needs a rule. So what exactly do you
propose to use which inode number for? I feel confused.
> And won't this string-based approach fail to detect file identity in the
> case of symbolic links as well?
Not sure what exactly you are asking here.
I'm trying to come up with a complete set of cases you might possibly mean:
The target exists and is a valid symbolic link:
$ echo Hello world! > bar
$ echo old news > old
$ ln -s old foo
$ make BAZ=/look/over/there
frob < bar > /look/over/there/foo
/bin/sh: cannot create /look/over/there/foo: No such file or directory
*** Error 1 in /home/schwarze/Test/make/branden_dot
(Makefile:6 '/look/over/there/foo')
The target exists but points into the woods:
$ rm -f foo
$ ln -s /invalid foo
$ make BAZ=/look/over/there
frob < bar > /look/over/there/foo
/bin/sh: cannot create /look/over/there/foo: No such file or directory
*** Error 1 in /home/schwarze/Test/make/branden_dot
(Makefile:6 '/look/over/there/foo')
The target does not exist, but is the target of a symbolic link:
$ rm -f foo old
$ make BAZ=/look/over/there
frob < bar > /look/over/there/foo
/bin/sh: cannot create /look/over/there/foo: No such file or directory
*** Error 1 in /home/schwarze/Test/make/branden_dot
(Makefile:6 '/look/over/there/foo')
The behaviour makes sense to me in all three cases.
Can you explain more clearly which exact scenario you worry about?
> Didn't BSD _invent_ symbolic links?
At least that's what our manual page claims, yes:
$ man symlink | grep -A1 ^H
HISTORY
The symlink() system call first appeared in 4.1cBSD. The symlinkat()
> Can someone make this make sense?
So far, unfortunately, no, i failed to make sense of your question...
Yours,
Ingo