X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/59bb51a638838ad413a4c89fb773df233aedb3b6..91f3a166f07e5f260310799a1e1687f45f3c9870:/GNUmakefile diff --git a/GNUmakefile b/GNUmakefile index f59c4d5..c601097 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,40 +1,48 @@ +# SPDX-FileCopyrightText: 2023, 2024 Lady +# SPDX-License-Identifier: MPL-2.0 + SHELL = /bin/sh # ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ override define makefileinfo ╭────────────────────────────╮ -╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞════════════════════════════════╗ +╔╡ ⁌ ⛩📰 书社 ∷ GNUmakefile ╞═════════════════════════════════╗ ║╰────────────────────────────╯ ║ ╟┬ ¶ Prerequisites ───────────────────────────────────────────┬╢ ║│ │║ -║│ Requires G·N·U Make, at least version 3.81, a version of │║ -║│ uuencode with base64 support, and the various programs │║ -║│ offered by libxml2 and libxslt. Beyond this, only programs │║ -║│ required by Posix are required, altho there is a chance of │║ -║│ version incompatibilities. The full list of program │║ +║│ Requires G·N·U Make, at least version 3.81, the Fine Free │║ +║│ File Command, and the various programs packaged with │║ +║│ libxml2 and libxslt. Beyond this, only programs specified │║ +║│ by Posix are required. The full list of program │║ ║│ requirements is as follows :— │║ ║│ │║ ║│ • awk │║ ║│ • cat │║ +║│ • cd │║ +║│ • cksum │║ ║│ • cp │║ ║│ • date │║ -║│ • echo │║ -║│ • file │║ +║│ • diff (with -u) │║ +║│ • file (specifically the Fine Free File Command) │║ ║│ • find │║ +║│ • grep │║ ║│ • git (optional) │║ -║│ • mkdir (requires support for `-p´) │║ +║│ • ln │║ +║│ • make (specifically G·N·U Make, version 3.81 or later) │║ +║│ • mkdir │║ ║│ • mv │║ -║│ • od (requires support for `-t x1´) │║ +║│ • od │║ +║│ • pax (ustar format, when generating archives) │║ ║│ • printf │║ ║│ • rm │║ ║│ • sed │║ ║│ • sleep │║ -║│ • stat │║ ║│ • test │║ ║│ • touch │║ -║│ • tr (requires support for `-d´) │║ -║│ • uuencode (requires support for `-m´ and `-r´) │║ -║│ • xargs (requires support for `-0´) │║ +║│ • tr │║ +║│ • uuencode │║ +║│ • uudecode │║ +║│ • xargs │║ ║│ • xmlcatalog (provided by libxml2) │║ ║│ • xmllint (provided by libxml2) │║ ║│ • xsltproc (provided by libxslt) │║ @@ -45,13 +53,14 @@ override define makefileinfo ║╰────────────────────────────────────────────────────────────╯║ ╟┬ ¶ Usage ───────────────────────────────────────────────────┬╢ ║│ │║ -║│ • `make all´: Compile, but do not install, all files. │║ +║│ • `make all´ (default): Compile, but do not install, all │║ +║│ files. │║ ║│ │║ ║│ • `make clean´: Remove `BUILDDIR´. │║ ║│ │║ -║│ • `make gone´: Remove installed files. │║ +║│ • `make gone´: Remove `BUILDDIR´ and installed files. │║ ║│ │║ -║│ • `make help´ (default): Print this message. │║ +║│ • `make help´: Print this message. │║ ║│ │║ ║│ • `make install´: Compile all files and install in │║ ║│ `DESTDIR´. │║ @@ -59,6 +68,13 @@ override define makefileinfo ║│ • `make list´: List all recognized source files and their │║ ║│ classification (including media type and dependencies). │║ ║│ │║ +║│ • `make listout´: List out the destinations of all │║ +║│ resulting files (relative to `DESTDIR´), e·g for │║ +║│ processing by another script. │║ +║│ │║ +║│ • `make uninstall´: Remove installed files, but not │║ +║│ `BUILDDIR´. │║ +║│ │║ ║│ Set `VERBOSE=1´ to see the text of commands as they are │║ ║│ executed. │║ ║│ │║ @@ -67,7 +83,7 @@ override define makefileinfo ║╰────────────────────────────────────────────────────────────╯║ ╟┬ ¶ Copyright & License ─────────────────────────────────────┬╢ ║│ │║ -║│ Copyright © 2023–2024 Lady [@ Lady’s Computer]. │║ +║│ Copyright © 2023–2024 Lady [@ Ladys Computer]. │║ ║│ │║ ║│ This Source Code Form is subject to the terms of the │║ ║│ Mozilla Public License, v 2.0. If a copy of the M·P·L was │║ @@ -81,112 +97,203 @@ endef # Programs needed to run this make·file. # -# If these are not installed on your computer, or you need to use a -# different implementation, you can override the appropriate variable. +# If these are not installed on your computer, or you need to use a different implementation, you can override the appropriate variable. AWK := awk CAT := cat +CD := cd +CKSUM := cksum CP := cp DATE := date -ECHO := echo +DIFF := diff FILE := file FIND := find GIT := git +GREP := grep +LN := ln MKDIR := mkdir MV := mv OD := od +PAX := pax PRINTF := printf RM := rm SED := sed SLEEP := sleep -STAT := stat TEST := test TOUCH := touch TR := tr +UUDECODE := uudecode UUENCODE := uuencode XARGS := xargs XMLCATALOG := xmlcatalog XMLLINT := xmllint XSLTPROC := xsltproc +# This variable is not used in normal execution. +# +# In the archiving `MODE´, it provides the X·M·L file from which the archive should be constructed. +SRC := + +# This variable is not used in normal execution. +# +# In the archiving `MODE´, it provides a user‐friendly name for the archive. +NAME := + # The directory which contains the source files. # -# Multiple directories can be given so long as files with the same name -# do not exist in each. +# Multiple directories can be given so long as files with the same name do not exist in each. SRCDIR := sources -# The directory which contains “includes”: Files which may be included -# in other files but for which no final output will be generated. +# The directory which contains “includes”: +# Files which may be included in other files but for which no final output will be generated. # -# Multiple directories can be given so long as files with the same name -# do not exist in each. +# Multiple directories can be given so long as files with the same name do not exist in each. # # These can be inside of `SRCDIR´ directories if desired. -INCLUDEDIR := sources/includes +# +# This variable is also used by the two‐stage `MODE´, where it gives the include directory for the second stage. +INCLUDEDIR := $(foreach dir,$(SRCDIR),$(dir)/includes) + +# The directory which contains the data files. +# +# Only used in the two‐stage `MODE´, and defaults to that `MODE´ if this directory exists. +# This directory is used as the include directory in the first stage. +# +# Multiple directories can be given so long as files with the same name do not exist in each. +DATADIR := # The directory in which to generate temporary buildfiles. +# +# This variable is also used by the archiving and two‐stage `MODE´s. BUILDDIR := build # The directory into which to output files on `make install´. +# +# This variable is also used by the archiving `MODE´. DESTDIR := public -# The location of this Makefile (and related ⛩️📰 书社 files), -# relative to the current working directory. +# The location of this Makefile (and related ⛩📰 书社 files), relative to the current working directory. # # By default, this is inferred from the variable `MAKEFILE_LIST´. +# +# This variable is also used by the archiving and two‐stage `MODE´s. THISDIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) -# The location of the magic files to use when determining media types. +# Configuration of `find´. # -# One is provided as part of this repository, but you can override it -# if you need different media type detection. +# By default, `find´ will ignore files which begin with a period and those which are likely to cause problems for `make´. # -# Your computer probably has a more comprehensive one installed at -# `/usr/share/file/magic´, but it is not recommended that you use this -# directly. Instead, link or copy just the files you expect to need for -# your project. -MAGICDIR := $(patsubst ./%,%,$(THISDIR)/magic) +# These variables are also used by the two‐stage `MODE´. +EXTRAFINDRULES := +EXTRAFINDINCLUDERULES := +FINDRULES := '!' '(' '(' -name '[.-]*' -a '!' -name '.' -o -name '*[][*?:|$$%\#\\; ]*' -o -name '*[)]' ')' -a -prune ')'$(if $(EXTRAFINDRULES), -a '(' $(EXTRAFINDRULES) ')',) +FINDINCLUDERULES := $(FINDRULES)$(if $(EXTRAFINDINCLUDERULES), -a '(' $(EXTRAFINDINCLUDERULES) ')',) -# Configuration of `find´. +# File extensions which indicate files in `SRCDIR´ which should be built as part of the first, rather than second, stage of the two‐stage `MODE´. +DATAEXT := rdf + +# Rules for identifying files in `SRCDIR´ which should be built as part of the first, rather than second, stage of the two‐stage `MODE´. +# +# By default, this just constructs rules from `DATAEXT´. +EXTRAFINDDATARULES := +FINDDATARULES := -name '.' $(foreach ext,$(DATAEXT), -o -name '$(subst ','"'"',[!.]*.$(ext))')$(if $(EXTRAFINDDATARULES), -a '(' $(EXTRAFINDDATARULES) ')',) + +# The list of magic files to use when determining media types. +# +# Some are provided as part of this repository, but you can add more if you need different media type detection. # -# By default, `find´ will ignore hidden files, those which begin with a -# period, and those which are likely to cause problems for `make´. -FINDRULES := -flags -nohidden -and ! '(' '(' -name '[.-]*' -or -name '*[][*?:|$$%\#;]*' ')' -and -prune ')' -FINDINCLUDERULES := $(FINDRULES) +# Your computer probably has several already installed at `/usr/share/file/magic´. +EXTRAMAGIC := +MAGIC := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/magic/*)) $(EXTRAMAGIC)) + +# The list of depedencies for parsers. +# +# When these files change, the assumption is that the result of the parsers will change as well, even if the parsers themselves have not. +EXTRAPARSERLIBS := +PARSERLIBS := $(sort $(THISDIR)/lib/split.xslt $(EXTRAPARSERLIBS)) # The list of parsers for plaintext file types. # -# Which parsers are provided will influence which kinds of files are -# recognized as plaintext. +# Which parsers are provided will influence which kinds of files are recognized as plaintext. +EXTRAPARSERS := +PARSERS := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/parsers/*.xslt)) $(EXTRAPARSERS)) + +# The list of depedencies for transforms. # -# Each parser ⁜must⁜ have a template which matches ⁜only⁜ X·H·T·M·L -# `')" +# (callable) Sanitize and wrap the provided plaintext file in X·M·L, printing to `stdout´. +override wrapplaintext = { $(PRINTF) '%s\n%s' '' ''; } -# ─ ¶ Phony Targets ─────────────────────────────────────────────────── +# (callable) Check if the provided X·M·L file is X·M·L 1.1, and if so, coerce to X·M·L 1.0 as best as possible, printing the result (or the original file contents) to `stdout´. +# +# The X·M·L declaration will be dropped and character escapes for C0 control codes will be replaced with a literal `U+0091 PRIVATE USE ONE´, which is invalid in X·M·L 1.1, but valid X·M·L 1.0 (making the replacement obvious). +# +# This isn’t a perfect substitution (it makes some assumptions about the format of the underlying X·M·L), but it should be workable for most sensible, welformed files. +override serializexml = $(SED) "$$($(PRINTF) '%b' '/]*?>//\n s/&\0043x0*[12345678BCEFbcef];/\0302\0221/g\n s/&\0043x0*1[0123456789ABCDEFabcdef];/\0302\0221/g\n s/&\00430*[12345678];/\0302\0221/g\n s/&\00430*1[12456789];/\0302\0221/g\n s/&\00430*2[0123456789];/\0302\0221/g\n s/&\00430*3[01];/\0302\0221/g\n}')" <$(call quote,$1) | $(SED) "$$($(PRINTF) '%b' ':a\n/^\\n*$$/{ $$d\n N\n ba\n}')" -# Provide help. -help: - @$(PRINTF) '%b' '$(subst $(newline),\n,$(makefileinfo))' +# (callable) Wraps the provided shell script in a conditional which checks whether a make restart caused by `$(BUILDDIR)/.update-types´ is imminent (i·e, it was created during the course of this run) and only executes the script if it is not. +override unlesstypeswillupdate = $(if $(call not,$(typeupdates)),if $(TEST) ! -e $(call quote,$(BUILDDIR)/.update-types); then ,)$1$(and $(call not,$(typeupdates)),; fi) + +# ─ ¶ Phony Targets ─────────────────────────────────────────────────── # Compile all files, or error if any are recursive. -all: $(call compiled,$(recursivefiles) $(compilablefiles) $(filter $(sourcefiles),$(assetfiles))); +all : $(call built,$(recursivefiles) $(installablefiles)) ; # Destroy buildfiles. -clean: - $(silent)$(RM) -rf $(BUILDDIR)/ +clean : + $(if $(BUILDDIR),$(silent)$(RM) -f -R $(call quote,$(BUILDDIR)/),) -# Destroy buildfiles and the install directory. -gone: - $(silent)$(RM) -rf $(BUILDDIR)/ $(call compiled,$(recursivefiles) $(compilablefiles)) +# Destroy build directory and installed files. +gone : clean uninstall ; -# Install the compiled files into `DESTDIR´. -install: $(call installed,$(recursivefiles) $(compilablefiles) $(filter $(sourcefiles),$(assetfiles))); +# Provide help. +help : + @$(PRINTF) '%b' '$(subst $(newline),\n,$(makefileinfo))' + +# Install the compiled files into `DESTDIR´, or error if any are recursive. +install : $(call installed,$(recursivefiles) $(installablefiles)) ; # List all source files and includes and their computed types. -list: +list : @$(PRINTF) '%b' $(call quote,$(foreach file,$(sort $(sourcefiles)) $(sort $(sourceincludes)),\0033[1m$(file)\0033[22m|$(call typeoffile,$(file))|[\0033[3m$(if $(filter $(file),$(xmlfiles)),xml,$(if $(filter $(file),$(plaintextfiles)),text,asset))$(if $(filter $(file),$(sourceincludes)),|include,)\0033[23m]$(if $(call dependencies,$(file))$(call recursives,$(file)), $(strip $(foreach recursive,$(call recursives,$(file)),\0033[93;41m•|Recursive|Dependency|\0033[39;49m|$(recursive)) $(foreach dependency,$(call dependencies,$(file)),\0033[2m•|Dependency|\0033[22m|$(dependency))))$(if $(filter $(file),$(sourcefiles)), →|<\0033[4m/$(call destination,$(file))\0033[24m>,) )) | $(TR) ' |' '\n ' -# Raise an error when attempting to build any files with recursive -# dependencies. -$(call compiled,$(recursivefiles)): - @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$(call uncompiled,$@)´ has recursive dependencies:\n$(subst |, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(call uncompiled,$@)),•|$(recursive))))) && false +# Lists out the destinations of all resulting files (relative to `DESTDIR´). +listout : + @$(PRINTF) '%s\n' $(call quote,$(foreach file,$(sort $(sourcefiles)),$(call destination,$(file)))) + +# Destroy installed files. +uninstall : + $(foreach file,$(installablefiles),$(if $(wildcard $(call installed,$(file))),$(silent)$(PRINTF) '%s\n' $(call quote,Removing …)$(newline)$(silent)$(RM) -f $(call quote,$(call installed,$(file)))$(newline),)) + +# Raise an error when attempting to build any files with recursive dependencies. +$(call built,$(recursivefiles)) : + @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$(call unbuilt,$@)´ has recursive dependencies:\n$(subst |, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(call unbuilt,$@)),•|$(recursive))))) >&2 && exit 1 + +# Add as a prerequisite to treat the target as tho it were phony. +FORCE : ; # ─ ¶ Special Targets ───────────────────────────────────────────────── -# Perform secondary expansion; this enables pattern rules to determine -# their prerequisites based on the matched pattern. -.SECONDEXPANSION: ; +# Perform secondary expansion; this enables pattern rules to determine their prerequisites based on the matched pattern. +.SECONDEXPANSION : ; # Don’t use any implicit rules. -.SUFFIXES: ; +.SUFFIXES : ; -# Phony rules; always consider these out‐of‐date. -.PHONY: all default clean gone info install list $(call compiled,$(recursivefiles)); +# Don’t delete these files even if Make is stopped in the process of rebuilding them. +.PRECIOUS : $(THISDIR)/GNUmakefile ; -ifneq ($(wildcard $(BUILDDIR)/.update-types)$(wildcard $(BUILDDIR)/dependencies),) -# Reload this make·file if the dependency graph has changed. -# -# This graph is a dependency for some of the variables that this -# make·file uses, so it’s important to ensure that they are actually -# up‐to‐date prior to executing any later rules. -# -# This recipe only exists after types have been updated or when the -# dependency graph already exists. -$(THISDIR)/GNUmakefile:: $(BUILDDIR)/dependencies $(BUILDDIR)/destinations - $(silent)$(TOUCH) $(THISDIR)/GNUmakefile - $(silent)$(RM) -f $(BUILDDIR)/.update-types - @$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' -endif +# Phony rules; always consider these out·of·date. +.PHONY : FORCE all clean gone help install list listout uninstall $(call built,$(recursivefiles)) ; -ifeq ($(wildcard $(BUILDDIR)/.update-types),) -# Reload this make·file if any of the magic files or parsers have -# changed. -# -# These are used to classify source files, so if they have changed then -# the make·file must be reloaded. +ifeq ($(notbuilding),) +# Reload this make·file if the magic file, parser, or transform have changed. # -# This recipe sleeps for one second to ensure that files built after -# the restart have a more current time·stamp. -$(THISDIR)/GNUmakefile:: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt +# If the magic file or parser changed, then another restart will be required, as the dependencies and destinations will need to be regenerated. +$(THISDIR)/GNUmakefile : $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt $(BUILDDIR)/transform.xslt $(silent)$(TOUCH) $(THISDIR)/GNUmakefile - $(silent)$(RM) -f $(call quote,$(BUILDDIR)/dependencies) - @$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m' - $(silent)$(SLEEP) 1 - $(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types) + $(if $(filter $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt,$?),$(call inform,$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m' >&2)$(newline)$(silent)$(SLEEP) 1,$(if $(typeupdates),$(silent)$(RM) $(call quote,$(BUILDDIR)/.update-types)$(newline),)$(call inform,$(PRINTF) '%b\n' '\0033[1mDependency graph$(comma) output destinations$(comma) or transforms updated. Restarting…\0033[22m' >&2)) endif # ─ ¶ Build Targets ─────────────────────────────────────────────────── +# Create symbolic links from the build directory’s store of magic files to their corresponding sources. +$(call magicfile,$(MAGIC)) : $(BUILDDIR)/magic/% : $$(call magicsource,$$@) + $(silent)$(call ensuredirectory,$(dir $@)) + $(silent)$(LN) -sf $(call quote,$(realpath $<)) $(call quote,$@) + # Generate the compiled magic file from its sources. # # It must be updated if any of the files in the magic directory change. -# It ⁜also⁜ should be updated if any of the files in the magic -# directory are deleted, but this isn’t tracked presently. -$(BUILDDIR)/magic.mgc: $(wildcard $(MAGICDIR)/*) - @$(ECHO) "Compiling new magic…" +$(BUILDDIR)/magic.mgc : $(call diffprereqs,magic,$(sort $(call magicfile,$(MAGIC)))) + $(foreach outdated,$(filter-out $^,$(wildcard $(BUILDDIR)/magic/*)),$(silent)$(RM) $(call quote,$(outdated))$(newline)) + $(call inform,$(PRINTF) '%s\n' 'Compiling new magic…' >&2) $(silent)$(call ensuredirectory,$(dir $@)) - $(silent)cd $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(MAGICDIR))) + $(silent)$(CD) $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(BUILDDIR)/magic)) + $(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types) # Generate the main parser. -$(BUILDDIR)/parser.catalog: $(PARSERS) - @$(ECHO) "Generating catalog of parsers…" +$(BUILDDIR)/parser.catalog : $(call diffprereqs,parsers,$(sort $(PARSERS))) + $(call inform,$(PRINTF) '%s\n' 'Generating catalog of parsers…' >&2) $(silent)$(XMLCATALOG) --create --noout $(call quote,$@) - $(foreach parser,$(PARSERS),$(silent)$(XMLCATALOG) --add uri $(call quote,$(call id,$(parser),parser)) $(call quote,file:///$(call pathenc,$(abspath $(parser)))) --noout $(call quote,$@)$(newline)) -$(BUILDDIR)/parser.xslt: $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2parser.xslt - @$(ECHO) "Generating main parser…" - $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<) + $(foreach parser,$(PARSERS),$(silent){ $(call id,$(parser)); $(PRINTF) '%s\n' $(call quote,$(call fileuri,$(parser))) '--noout' $(call quote,$@); } | $(xargsmultiquote) | $(XARGS) -E '' $(XMLCATALOG) --add uri$(newline)) +$(BUILDDIR)/parser.xslt : $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2parser.xslt + $(call inform,$(PRINTF) '%s\n' 'Generating main parser…' >&2) + $(silent)$(XSLTPROC) --nonet --novalid --nomkdir --nowrite $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<) >|$(call quote,$@) + $(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types) -# Parse the files. +# Generate R·D·F metadata for files. # -# Even plain X·M·L files are parsed, because they may contain X·H·T·M·L -# `