sudo make install

I originally wrote this in April 2022, but only published it now because... reasons.

I found a weird bug. It occurred while installing neatroff, which is a nice, new, tidy implementation of troff. It can be used from the source dir itself, or can be installed to a $(BASE) like /usr/local/share/neatroff.

But when I was trying to compile some documents, the neatpdf PDF postprocessor couldn't find the requested fonts and it looked all wrong with weird or no fonts. Everything was installed in $(BASE) correctly. I could compile the same document in the git source directory and get the right results, so it had to be something to do with the way in which contents of files are changed for being used from $(BASE).

The font definitions contain a line fontpath /path/to/font so that PDFs know where to find the fonts. This is written to using a sed call. Neatroff was meant to only be used from the source dir, so font files usually say fontpath $(PWD)/fonts, where $(PWD) hopefully refers to the toplevel source directory (ie. you're running make install not make -f ../Makefile install).

To permit installing neatroff to the system, the sed call changes fontpath $(PWD)/fonts to fontpath $(BASE)/fonts:

@for f in "$(BASE)/devutf"/*; do \
    sed "/^fontpath /s=$(PWD)/fonts=$(BASE)/fonts=" <$$f >.fd.tmp \
    mv .fd.tmp $$f \

All fine, you might think.

Consider where the makefile actually gets $(PWD) from. It isn't a defined macro at the top (PWD = ...), so it inherits it from the environment, which is a shell, where we can rely on $PWD being set. All fine.

This is how GNU make does it - it gets $(PWD) from its parent process - usually, your shell. BSD make sets $(PWD) explicitly.

However, when you run make under sudo (or doas, or ssu), these privilege-escalting tools do not set $PWD in their default configuration. Therefore, GNU make can't find a value for $(PWD), so it is empty.

The line the sed calls act on start by looking something like

fontpath /home/me/src/neatroff_make/fonts/NimbusRoman-Regular.t1

and should be turned into something like

fontpath /usr/local/share/neatroff/fonts/NimbusRoman-Regular.t1

but under sudo with gmake, with $(PWD) unset, it actually becomes

fontpath /fonts/NimbusRoman-Regular.t1

which clearly doesn't work.

There are ways to make sudo et al. keep $PWD defined (by using the -E flag, or a config line) but that's not something that should be relied on when doing sudo make install.

Luckily, POSIX requires that shells set PWD themselves, so we can always find the name of the current working directory by doing $$(pwd) (which runs the pwd program), or $$PWD (which is given to a shell as $PWD, which is dereferenced to what we want). Interestingly, both BSD make and GNU make set special variables with the value of PWD: BSD make sets $(.CURDIR), and GNU make sets $(CURDIR). See the difference? It's really annoying that the two biggest make implementations have this slight inconsistency, and POSIX doesn't require any special make macro with the value of PWD. (OpenGroup members, something for POSIX.1-202x?)

I chose to go with $$PWD:

PWD = $${PWD}

Now, anywhere in the Makefile that uses $(PWD) gets sent to the shell as ${PWD}, which is fine.

There's one last problem: the change from using $(PWD), which is dereferenced by make, to $$PWD, which is dereferenced by the shell running each rule line, caused another problem with lines like

@cd neatmkfn && ./ "$(PWD)/fonts" "$(PWD)/devutf" >/dev/null

which cd somewhere before doing something. They get $PWD of the shell in the new directory, but we want the old directory (the $PWD of the Makefile). I thought the most simple way to fix this was with a shell variable, like

@pwd="$(PWD)" && \
	cd neatmkfn && ./ "$$pwd/fonts" "$$pwd/devutf" >/dev/null

This is a little bit annoying but it is done to preserve the portability of the Makefile (it is POSIX.1-2017 compliant). Both GNU and BSD make have syntaxes for immediate-expansion macro definitions. These would fix this problem because it would save the string of the current working directory into $(PWD), rather than a kind of nested macro which gets dereferenced later. (Note that the syntax is mostly incompatible between GNU and BSD make).

For example, we could do

PWD != pwd

which runs pwd then and there, and saves it. However, this is not in POSIX (currently).

In my opinion, the best solution that POSIX.1-202x should use is to require make to set the PWD macro itself. The whole SCCS stuff in POSIX already requires make to think about PWD, so I don't see why this shouldn't happen. PWD is a better macro than CURDIR or .CURDIR because of the discrepancies between GNU and BSD make.

The commit fixing this bug.

written 2022-12-28, originally 2022-04-17


Blog home