# SPDX-License-Identifier: 0BSD
CXX ?= c++
CC ?= cc
MANDOC ?= mandoc
AWK ?= awk
SED ?= sed
SHELLLCHECK ?= shellcheck
SYMLINK ?= n
LTO ?= y
VOREUTILS_VERSION ?= $(shell git describe --always)
# Date modes:
# * git-global – git log -1
# * git-file – git log -1 $file-at-hand
# * stat – stat changed $file-at-hand
# * constant-epoch – $VOREUTILS_DATE – seconds since epoch
# * constant-literal – $VOREUTILS_DATE – must be in "%B %e, %Y" (Fullmonth space-padded-day-number, YYYY) format
VOREUTILS_DATE_MODE ?= git-file
VOREUTILS_DATE ?=
CFLAGS ?=
CPPFLAGS ?=
LDFLAGS ?=
OUTDIR ?= out/
OBJDIR ?= $(OUTDIR)obj/
CMDDIR ?= $(OUTDIR)cmd/
LIBDIR ?= $(OUTDIR)lib/
MANDIR ?= $(OUTDIR)man/
HTMLMANDIR ?= $(OUTDIR)man-html/
VOREUTILS_LIB_PREFIX ?= /usr/lib/voreutils/
HTMLMAN_OUTSIDE_FORMAT ?= https://manpages.debian.org/bullseye/%N.%S
# K/V list of the diff between the default mandoc style and ours; grep for "mandoc compat": https://inbox.vuxu.org/mandoc-discuss/20220529001500.gnxuiiawdegcjwy7@tarta.nabijaczleweli.xyz/T/
HTMLMAN_FONT_OVERRIDES ?= Ar CI Nm CB
DATE_EPOCH := date $(shell date --date @0 > /dev/null 2>&1 && echo "--date @" || echo "-r ")
STAT_CHANGED := stat $(shell stat -c"%i" /dev/null > /dev/null 2>&1 && echo "-c%Y" || echo "-f%c")
ifeq "$(VOREUTILS_DATE_MODE)" "git-global"
DATE_F := $(shell $(DATE_EPOCH)$(shell git log -1 --no-show-signature --format=%at) +"%B %e, %Y")
else ifeq "$(VOREUTILS_DATE_MODE)" "git-file"
DATE_F = $(shell $(DATE_EPOCH)$(shell git log -1 --no-show-signature --format=%at "$(1)") +"%B %e, %Y")
else ifeq "$(VOREUTILS_DATE_MODE)" "stat"
DATE_F = $(shell $(DATE_EPOCH)$(shell $(STAT_CHANGED) "$(1)") +"%B %e, %Y")
else ifeq "$(VOREUTILS_DATE_MODE)" "constant-epoch"
DATE_F := $(shell $(DATE_EPOCH)$(VOREUTILS_DATE) +"%B %e, %Y")
else ifeq "$(VOREUTILS_DATE_MODE)" "constant-literal"
DATE_F := $(VOREUTILS_DATE)
else
$(error VOREUTILS_DATE_MODE=$(VOREUTILS_DATE_MODE) must be any of git-{global,file}, stat, constant-{epoch,literal})
endif
ifeq "$(VOREUTILS_DATE_MODE)" "git-file"
INDEX_PAGE_DATE := $(call DATE_F,man/)
else ifeq "$(VOREUTILS_DATE_MODE)" "stat"
INDEX_PAGE_DATE := $(shell date +"%B %e, %Y")
else
INDEX_PAGE_DATE := $(DATE_F)
endif
ifneq "$(subst constant-,,$(VOREUTILS_DATE_MODE))" "$(VOREUTILS_DATE_MODE)"
ifeq "$(VOREUTILS_DATE)" ""
$(error VOREUTILS_DATE_MODE=$(VOREUTILS_DATE_MODE) and no date specified)
endif
endif
AS_NEEDED := -Wl,--as-needed
SO := .so
SONAME := -soname
SYSTEM := $(shell uname)
ifeq "$(SYSTEM)" "NetBSD"
#
else ifeq "$(SYSTEM)" "OpenBSD"
#
else ifeq "$(SYSTEM)" "Darwin"
AS_NEEDED :=
SONAME := -install_name
SO := .dylib
else ifeq "$(SYSTEM)" "Linux"
CXXSPECIFICCC += -D_FILE_OFFSET_BITS=64
else ifeq "$(SYSTEM)" "SunOS"
AS_NEEDED :=
CXXSPECIFICCC += $(shell getconf LFS_CFLAGS)
CXXSPECIFICLD += $(shell getconf LFS_LDFLAGS) $(shell getconf LFS_LIBS)
else
#
endif
CXXVER := $(shell $(CXX) --version | head -1)
ifneq "$(findstring clang,$(CXXVER))" ""
# GCC doesn't have this granularity
CXXSPECIFICCC += -Wpedantic -Wno-c++20-designator -Wno-c99-extensions -Wno-unknown-warning-option
ifneq "$(findstring Apple,$(CXXVER))" ""
# TODO: remove if we don't end up using CMSG_SPACE()
# CMSG_SPACE() is a macro, and it's decidedly constexpr. i don't care if appleclang can't figure that out
CXXSPECIFICCC += -Wno-vla-extension
endif
CXXSPECIFICCC += -Wno-gnu-conditional-omitted-operand
ifeq "$(LTO)" "y"
CXXSPECIFICLD += -flto=full # Clang produces .o files with LLVM bitcode, which cannot be linked to if put in .as
else
CXXSPECIFICLD +=
endif
else
CXXSPECIFICCC += -Wno-missing-field-initializers -fno-common
ifeq "$(LTO)" "y"
CXXSPECIFICCC += -flto
endif
CXXSPECIFICLD +=
endif
ifeq "$(SYMLINK)" "y"
SYMFLAG := s
endif
CCAR := -g -O3 -pipe -Wall -Wextra -fPIC -fno-math-errno $(CXXSPECIFICCC) $(CFLAGS) $(shell pkg-config --cflags libselinux 2>/dev/null && echo -DVOREUTILS_LIBSELINUX) $(shell for l in b2 crypto; do pkg-config --cflags "lib$$l" 2>/dev/null; done)
LDAR := $(AS_NEEDED) -L$(OUTDIR) $(CXXSPECIFICLD) $(LDFLAGS) $(shell pkg-config --libs libselinux 2>/dev/null ) $(shell for l in b2 crypto; do pkg-config --libs "lib$$l" 2>/dev/null || echo "-l$$l"; done)
CPPAR = -Iinclude/ -DVOREUTILS_VERSION='"$(VOREUTILS_VERSION)"' -DVOREUTILS_DATE='"$(call DATE_F,$(1))"' -DVOREUTILS_SO='"$(SO)"' -DVOREUTILS_LIB_PREFIX='"$(VOREUTILS_LIB_PREFIX)"' -include vore-id.h $(CPPFLAGS)
BINARIES := $(filter-out cmd/aliases,$(wildcard cmd/*))
LIBRARIES := $(wildcard lib/*)
INCLUDES := $(wildcard include/*)
MANPAGES := $(wildcard man/*.?)
.PHONY : all binaries libraries manpages htmlpages clean test
.SECONDARY:
all : binaries libraries manpages htmlpages
allpages : manpages htmlpages
clean:
rm -rf $(OUTDIR) $(OBJDIR)
export CXX CC
test : binaries libraries
@mkdir -p $(OBJDIR)locales/
CMDDIR='$(realpath $(CMDDIR))/' LOCDIR='$(realpath $(OBJDIR))/locales/' find "tests/" -mindepth 1 -maxdepth 1 -type f -print -execdir ./{} \; 3>$(OBJDIR)testpsko
CMDDIR='$(realpath $(CMDDIR))/' LOCDIR='$(realpath $(OBJDIR))/locales/' find "tests/" -mindepth 2 -maxdepth 2 -type f -name test -print -execdir ./{} \; 3>>$(OBJDIR)testpsko
@rm -rf LOCDIR=$(OBJDIR)locales/
@cat $(OBJDIR)testpsko >&2
@! [ -s $(OBJDIR)testpsko ]
rewrap_mandirs = $(patsubst %.8,man8/%.8,$(patsubst %.7,man7/%.7,$(patsubst %.6,man6/%.6,$(patsubst %.5,man5/%.5,$(patsubst %.4,man4/%.4,$(patsubst %.3,man3/%.3,$(patsubst %.2,man2/%.2,$(patsubst %.1,man1/%.1,$(patsubst %.0,man0/%.0,$(patsubst man/%,%,$(1)))))))))))
binaries : $(patsubst %.sh,%,$(patsubst %.cpp,%,$(patsubst cmd/%,$(CMDDIR)%,$(BINARIES)))) $(OBJDIR)aliases
libraries : $(patsubst %.cpp,%$(SO),$(patsubst lib/%,$(LIBDIR)%,$(LIBRARIES)))
manpages : $(patsubst %,$(MANDIR)%,$(call rewrap_mandirs,$(MANPAGES))) $(OBJDIR)man/aliases
htmlpages : $(patsubst %,$(HTMLMANDIR)%.html,$(call rewrap_mandirs,$(MANPAGES) index.0)) $(OBJDIR)man/aliases $(HTMLMANDIR)style.css
$(OBJDIR)man/aliases : man/aliases
@mkdir -p $(@D) $(MANDIR) $(HTMLMANDIR)
$(AWK) '!/^$$/ { \
tsec = substr($$1, length($$1)); \
for(i = 2; i <= NF; ++i) { \
lsec = substr($$i, length($$i)); \
rel = (tsec == lsec) ? "" : "../man" tsec "/"; \
print "(mkdir -p man" tsec " man" lsec " && cd man" lsec " && set -x && ln -fs " rel $$1 "$$suff " $$i "$$suff)"; \
} \
}' $^ > $@
{ cd $(MANDIR) && suff= sh; } < $@
{ cd $(HTMLMANDIR) && suff=".html" sh; } < $@
# This ordering is suboptimal
$(OBJDIR)aliases : cmd/aliases $(patsubst %.sh,%,$(patsubst %.cpp,%,$(patsubst cmd/%,$(CMDDIR)%,$(BINARIES))))
@mkdir -p $(@D) $(CMDDIR)
$(AWK) '!/^$$/ { for(i = 2; i <= NF; ++i) print "ln -f$(SYMFLAG) " $$1 " " $$i }' $< > $@
{ cd $(CMDDIR) && sh -x; } < $@
# The d-v-o-s string starts at "BSD" (hence the "BSD General Commands Manual" default); we're not BSD, so hide it
# Can't put it at the very top, since man(1) only loads mdoc *after* the first mdoc macro (.Dd in our case)
$(OBJDIR)man/% : man/%
@mkdir -p $(@D) $(dir $(patsubst %,$(MANDIR)%,$(call rewrap_mandirs,$<)))
$(AWK) '$$0 == ".Dd" {$$2 = "$(call DATE_F,$<)"} $$1 == ".Dt" { print ".ds doc-volume-operating-system" } $$0 == ".Os" {$$2 = "voreutils"; $$3 = "$(VOREUTILS_VERSION)"} {gsub(/@VOREUTILS_LIB_PREFIX@/, "$(VOREUTILS_LIB_PREFIX)"); gsub(/@VOREUTILS_SO@/, "$(SO)"); sub(/^\^/, "\\(ha"); gsub(/([^\\])\^/, "&_BUT-HA_"); gsub(/\^_BUT-HA_/, "\\(ha"); print}' $< | tee $(patsubst %,$(MANDIR)%,$(call rewrap_mandirs,$<)) > $@
! $(MANDOC) -Tlint $@ 2>&1 | grep -vE -e 'mandoc: outdated mandoc.db' -e 'STYLE: referenced manual not found' -e 'WARNING: cross reference to self' -e 'WARNING: undefined string, using "": doc-str-St$$' -e 'WARNING: undefined string, using "": doc-Tn-font-size' -e 'STYLE: useless macro: Tn' -e 'WARNING: unknown font, skipping request: TS.+fC[RBI]' -e 'STYLE: input text line longer than 80 bytes:' -e 'WARNING: nested displays are not portable: D1 in Bd' -e 'STYLE: operating system explicitly specified: Os voreutils' $(shell $(AWK) '/mandoc-ignore/ {$$1 = ""; $$2 = "-e"; $$3 = "\"" $$3; $$NF = $$NF "\""; print} /^.\\"$$/ {exit}' $<)
# The "WARNING: unknown font, skipping request: TS.+fC[RBI]" one: see https://bugs.debian.org/992002
# GNU Make needs an empty rule here so that it actually picks this up as an orderable rule, not a strict, unfulfillable, dependency
$(foreach l,1 2 3 4 5 6 7 8,$(MANDIR)man$(l)/%) : $(OBJDIR)man/%
@
$(OBJDIR)manalias/es : man/ man/aliases
@mkdir -p $(@D)
cd $(OBJDIR)manalias && rm -f * && touch $(sort $(subst man/,,$(MANPAGES)) $(shell cat man/aliases)) es
$(OBJDIR)man/index.0 : $(MANPAGES)
{ \
printf "%s\n" ".Dd $(INDEX_PAGE_DATE)" ".Dt INDEX 0" ".Os voreutils $(VOREUTILS_VERSION)" ".Sh NAME" ".Nm index" ".Nd voreutils $(VOREUTILS_VERSION) manual index" ".Sh CONTENTS" "." '.Bl -tag -compact'; \
awk '/^\.Nm/ {sub(/\./, " ", FILENAME); sub("man/", ".It Xr ", FILENAME); print FILENAME; print} /^\.Nd/ {sub(/\.Nd/, "\\-"); print; nextfile}' $(shell printf "%s\n" $(call rewrap_mandirs,$^) | sort -n | cut -c -3,5-); \
echo .El; \
} > $@
# mandoc always evaluates n to true and t to false
$(foreach l,0 1 2 3 4 5 6 7 8,$(HTMLMANDIR)man$(l)/%.html) : $(OBJDIR)man/% $(OBJDIR)manalias/es
@mkdir -p $(@D)
printf '.ds doc-%s-font \\f[%s]\n' $(HTMLMAN_FONT_OVERRIDES) | \
$(SED) -e 's/^\(\.i[ef]\) n/\1 0/' -e '/^\.Sh DESCRIPTION/r /dev/stdin' $< | \
(cd $(OBJDIR)manalias; $(MANDOC) -Thtml -Ostyle="../style.css",man="../man%S/%N.%S.html;$(HTMLMAN_OUTSIDE_FORMAT)") | \
$(AWK) '/^<h1/ {in_syn = $$0 ~ /id="SYNOPSIS"/} /^<br/ {if(in_syn) next} {print}' | \
$(SED) -Ee 's/ title=".."//g' -e 's/<a class="permalink" href="#([^"]*)"><span class="No" id="([^"]*)">/<a><span class="No">/g' > $@
$(HTMLMANDIR)style.css : man/style.css
@mkdir -p $(@D)
cp $^ $@
$(OBJDIR)cmd/%.sh : cmd/%.sh
$(SED) '/SPDX-License-Identifier/a\
# voreutils $(VOREUTILS_VERSION) ($(call DATE_F,$<))' < $< > $@
chmod +x $@
$(SHELLLCHECK) $@
$(CMDDIR)% : $(OBJDIR)cmd/%.sh
@mkdir -p $(@D)
cp $< $@
$(CMDDIR)% : $(OBJDIR)cmd/%.o
@mkdir -p $(@D)
$(CXX) $(CCAR) -fno-exceptions -o $@ $< $(LDAR)
$(LIBDIR)%$(SO) : $(OBJDIR)lib/%.o
@mkdir -p $(@D)
$(CXX) $(CCAR) -shared -o $@ $< $(LDAR) -Wl,$(SONAME),$(subst $(OUTDIR),,$@).$(VOREUTILS_VERSION)
$(OBJDIR)%.o : %.cpp $(INCLUDES)
@mkdir -p $(@D)
$(CXX) $(CCAR) $(call CPPAR,$<) -std=c++17 -fno-exceptions -c -o $@ $<