X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/2024d501192c7399c0791dec5ba0d3004613aefc..dc9d0a5796562d876c30b8766fd9d544bfaf7b63:/GNUmakefile?ds=sidebyside
diff --git a/GNUmakefile b/GNUmakefile
index 75045a9..6589d59 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -25,11 +25,13 @@ override define makefileinfo
║│ • echo │║
║│ • file │║
║│ • find │║
+║│ • grep │║
║│ • git (optional) │║
║│ • ln │║
║│ • mkdir (requires support for `-p´) │║
║│ • mv │║
║│ • od (requires support for `-t x1´) │║
+║│ • pax (only when generating archives) │║
║│ • printf │║
║│ • rm │║
║│ • sed │║
@@ -39,6 +41,7 @@ override define makefileinfo
║│ • touch │║
║│ • tr (requires support for `-d´) │║
║│ • uuencode (requires support for `-m´ and `-r´) │║
+║│ • uudecode (requires support for `-m´ and `-r´) │║
║│ • xargs (requires support for `-0´) │║
║│ • xmlcatalog (provided by libxml2) │║
║│ • xmllint (provided by libxml2) │║
@@ -100,10 +103,12 @@ ECHO := echo
FILE := file
FIND := find
GIT := git
+GREP := grep
LN := ln
MKDIR := mkdir
MV := mv
OD := od
+PAX := pax
PRINTF := printf
RM := rm
SED := sed
@@ -112,12 +117,23 @@ 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.
@@ -132,14 +148,20 @@ SRCDIR := sources
INCLUDEDIR := sources/includes
# The directory in which to generate temporary buildfiles.
+#
+# This variable is also used by the archiving `MODE´.
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.
#
# By default, this is inferred from the variable `MAKEFILE_LIST´.
+#
+# This variable is also used by the archiving `MODE´.
THISDIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
# Configuration of `find´.
@@ -183,13 +205,25 @@ SRCREV := $(shell $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/
endif
endif
+# The mode in which to run this make·file.
+# Modes essentially allow for the combination of multiple conceptual make·files into a single source.
+#
+# The following modes are available :—
+#
+# • ‹ urn:fdc:ladys.computer:20231231:Shu1She4:mode:default ›:
+# The default mode; typical Shushe behaviours.
+#
+# • ‹ urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive ›:
+# Generates archive files from parse results.
+MODE := urn:fdc:ladys.computer:20231231:Shu1She4:mode:default
+
# Set to a non·empty value to print all commands as they run.
VERBOSE :=
# The default target for this makefile.
.DEFAULT_GOAL := all
-# ━ § BEGIN MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# ━ § BEGIN SHARED MAKE·FILE CONSTRUCTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
@@ -214,6 +248,33 @@ override quote = '$(subst ','"'"',$1)'
# Outputs an `@´ to silence rules, unless `VERBOSE´ is nonempty.
override silent := $(if $(VERBOSE),,@)
+# (callable) Get the modified time of the provided file.
+#
+# This is messy; there is no portable way of using `stat´.
+#
+# ☡ This variable creates a subshell every time it is computed.
+override modtime = $(shell if $(STAT) --version 2> /dev/null | $(GREP) -q GNU; then $(DATE) -d "@$$(TZ= $(STAT) -c '%Y' $(call quote,$1))" '+%Y-%m-%dT%H:%M:%SZ'; else TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$1); fi)
+
+# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
+
+# (callable) Check to see if the given directory exists and create it if not.
+override ensuredirectory = if $(TEST) ! -d $(call quote,$1); then $(MKDIR) -p $(call quote,$1); fi
+
+# (callable) Test if the provided xpath expression matches the provided document.
+override xpath = $(XMLLINT) --xpath $(call quote,$1) $(call quote,$2) > /dev/null 2> /dev/null
+
+# (callable) Extract the value of the text nodes in the provided X·M·L document and print them to `stdout´.
+override extracttext = $(PRINTF) '%s' '' | $(XSLTPROC) --nonet --novalid - $(call quote,$1)
+
+# (callable) Process the provided transformation result and output the result to the provided location, given the provided relative path.
+override processresultto = if $(call xpath,/*[local-name()="raw-text" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(RM) -f $(call quote,$2); $(call extracttext,$1) > $(call quote,$2); elif $(call xpath,/*[local-name()="base64-binary" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(RM) -f $(call quote,$2); $(call extracttext,$1) | $(TR) -d '\t\n\f\r ' | $(XARGS) -0 $(PRINTF) 'begin-base64 644 -\n%s\n====\n' | $(UUDECODE) -o /dev/stdout > $(call quote,$2); elif $(call xpath,/*[local-name()="archive" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then $(RM) -f $(call quote,$2); $(MAKE) -f $(call quote,$(abspath $(THISDIR)/GNUmakefile)) $(call quote,$2) NAME=$(call quote,$3) SRC=$(call quote,$1) BUILDDIR=$(call quote,$(BUILDDIR)/archive/$3) DESTDIR=$(call quote,$(patsubst %/,%,$(dir $2))) MODE='urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive'; else $(RM) -f $(call quote,$2); $(XMLLINT) --nsclean $(call quote,$1) > $(call quote,$2); fi
+
+# ━ § BEGIN DEFAULT MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:default)
+
+# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
+
# (callable) Test to see if the prerequisites provided by the second argument matches the value in the file corresponding to the first argument in `$(BUILDDIR)/lastprereqs´.
# If not, save the new value and then add FORCE.
# Return them regardless.
@@ -257,7 +318,7 @@ sourcefiles := $(filter-out $(sourceincludes),$(shell $(FIND) $(foreach dir,$(SR
# Figure out the file type of each source file and source include.
ifneq ($(wildcard $(BUILDDIR)/magic.mgc),)
-override types := $(shell $(SED) 's/^ *//;s/ *$$//;s/ {2,}/ /g' <<< $(call quote,$(sourcefiles) $(sourceincludes)) | $(TR) ' ' '\n' | $(FILE) -m $(call quote,$(BUILDDIR)/magic.mgc) --mime-type --separator '|' --files-from - | $(SED) 's/| */|/g')
+override types := $(shell $(PRINTF) '%s\n' $(call quote,$(sourcefiles) $(sourceincludes)) | $(SED) 's/^ *//;s/ *$$//;s/ {2,}/ /g' | $(TR) ' ' '\n' | $(FILE) -m $(call quote,$(BUILDDIR)/magic.mgc) --mime-type --separator '|' --files-from - | $(SED) 's/| */|/g')
endif
# Get the list of supported plaintext file types from the parser.
@@ -294,7 +355,7 @@ override includepath = $(firstword $(foreach directory,$(INCLUDEDIR),$(if $(filt
# (callable) Get base64 data u·r·i’s for the given files.
#
# ☡ This variable creates a subshell every time it is computed.
-override datauri = $(foreach file,$1,data:$(call typeoffile,$(file));base64,$(shell $(UUENCODE) -m -r $(call quote,$(file)) _ | tr -d ' \n'))
+override datauri = $(foreach file,$1,data:$(call typeoffile,$(file));base64,$(shell $(UUENCODE) -m $(call quote,$(file)) _ | $(SED) '2,$$!d;$d' | $(TR) -d ' \n'))
# Pair each source file and include with its local u·r·i.
override sourcelocalpair := $(foreach file,$(sourcefiles) $(sourceincludes),$(file)|about:shushe?$(if $(filter $(file),$(sourceincludes)),include=$(call pathenc,$(call includepath,$(file))),source=$(call pathenc,$(call sourcepath,$(file)))))
@@ -366,23 +427,26 @@ override sourcedestinationpair := $(foreach destination,$(destinations),$(call s
override destination = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcedestinationpair))))
# Pair each source file with its compiled location.
-override sourcecompiledpair := $(foreach file,$(sourcefiles),$(file)|$(BUILDDIR)/public/$(call destination,$(file)))
+override sourcecompiledpair := $(foreach file,$(sourcefiles),$(file)|$(BUILDDIR)/results/$(call destination,$(file)))
# (callable) Get the location of the transformed X·M·L files for the given source files.
override compiled = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcecompiledpair))))
-# (callable) Get the source files for the given compiled file.
+# (callable) Get the location of the source files for the given compiled file.
override uncompiled = $(foreach file,$1,$(patsubst %|$(file),%,$(filter %|$(file),$(sourcecompiledpair))))
+# (callable) Get the location of the final built files for the given source files.
+override built = $(foreach file,$1,$(patsubst $(BUILDDIR)/results/%,$(BUILDDIR)/public/%,$(call compiled,$(file))))
+
+# (callable) Get the location of the source files for the given built files.
+override unbuilt = $(foreach file,$1,$(call uncompiled,$(patsubst $(BUILDDIR)/public/%,$(BUILDDIR)/results/%,$(file))))
+
# (callable) Get the installed locations for the given source files.
override installed = $(foreach file,$1,$(DESTDIR)/$(call destination,$(file)))
endif
# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
-# (callable) Check to see if the given directory exists and create it if not.
-override ensuredirectory = if $(TEST) ! -d $(call quote,$1); then $(MKDIR) -p $(call quote,$1); fi
-
# (callable) Get the identifier for the given parser or transform.
override id = $(XMLLINT) --xpath '/*/*[local-name()="id" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"]/text()[1]' $(call quote,$1) 2> /dev/null || $(PRINTF) '%s\n' $(call quote,about:shushe?$(or $2,unknown)=$(call pathenc,$(basename $(notdir $1))))
@@ -394,12 +458,12 @@ override wrapplaintext = $(TR) '\000\013\014' '\032\011\012' < $(call quote,$1)
# 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*[1-8BCEFbcef];/\0302\0221/g\n s/&\0043x0*1[0-9A-Fa-f];/\0302\0221/g\n s/&\00430*[1-8];/\0302\0221/g\n s/&\00430*1[124-9];/\0302\0221/g\n s/&\00430*2[0-9];/\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}')"
+override serializexml = $(SED) "$$($(PRINTF) '%b' '/]*?>//\n s/&\0043x0*[1-8BCEFbcef];/\0302\0221/g\n s/&\0043x0*1[0-9A-Fa-f];/\0302\0221/g\n s/&\00430*[1-8];/\0302\0221/g\n s/&\00430*1[124-9];/\0302\0221/g\n s/&\00430*2[0-9];/\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}')"
# ─ ¶ Phony Targets ───────────────────────────────────────────────────
# Compile all files, or error if any are recursive.
-all : $(call compiled,$(recursivefiles) $(compilablefiles)) ;
+all : $(call built,$(recursivefiles) $(installablefiles)) ;
# Destroy buildfiles.
clean :
@@ -424,8 +488,8 @@ uninstall :
$(foreach file,$(installablefiles),$(if $(wildcard $(call installed,$(file))),$(silent)$(PRINTF) '%s\n' $(call quote,Removing $(patsubst $(DESTDIR)/%,%,$(call installed,$(file)))>…)$(newline)$(silent)$(RM) -f $(call quote,$(call installed,$(file)))$(newline),))
# 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
+$(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))))) && false
# Add as a prerequisite to treat the target as tho it were phony.
FORCE : ;
@@ -439,7 +503,7 @@ FORCE : ;
.SUFFIXES : ;
# Phony rules; always consider these out·of·date.
-.PHONY : FORCE all default clean gone info install list uninstall $(call compiled,$(recursivefiles)) ;
+.PHONY : FORCE all default clean gone info install list uninstall $(call built,$(recursivefiles)) ;
ifneq ($(typeupdates)$(wildcard $(BUILDDIR)/dependencies)$(wildcard $(BUILDDIR)/destinations),)
# Reload this make·file if the dependency graph or output destinations have changed.
@@ -497,7 +561,7 @@ $(BUILDDIR)/parser.catalog : $(call diffprereqs,parsers,$(sort $(PARSERS)))
$(foreach parser,$(PARSERS),$(silent)( $(call id,$(parser)) ) | $(XARGS) -I %% $(XMLCATALOG) --add uri %% $(call quote,$(call fileuri,$(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,$<)
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<)
$(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types)
# Parse the files.
@@ -507,7 +571,7 @@ $(BUILDDIR)/parser.xslt : $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2pars
$(call parsed,$(sourcefiles) $(sourceincludes)) : % : $$(call unparsed,$$@) $(BUILDDIR)/parser.xslt $(typeupdates)
@$(PRINTF) '%s\n' $(call quote,Processing `$<´…)
$(silent)$(call ensuredirectory,$(dir $@))
- $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(call serializexml,$<)) | $(XSLTPROC) -o $(call quote,$@) --stringparam BUILDTIME $$(TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ') --stringparam IDENTIFIER $(call quote,$(call localuri,$<)) --stringparam SRCTIME $$(TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$<)) --stringparam CKSUM $$($(CKSUM) $(call quote,$<) | $(SED) 's/[ ].*//')$(if $(THISREV), --stringparam THISREV $(call quote,$(THISREV)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/parser.xslt) -)
+ $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(call serializexml,$<)) | $(XSLTPROC) --nonet --novalid -o $(call quote,$@) --stringparam BUILDTIME $$(TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ') --stringparam IDENTIFIER $(call quote,$(call localuri,$<)) --stringparam SRCTIME '$(call modtime,$<)' --stringparam CKSUM $$($(CKSUM) $(call quote,$<) | $(SED) 's/[ ].*//')$(if $(THISREV), --stringparam THISREV $(call quote,$(THISREV)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/parser.xslt) -)
# Generate a catalog of all parsed files, for use when processing includes.
#
@@ -520,7 +584,8 @@ $(BUILDDIR)/catalog : $(call diffprereqs,sources,$(sort $(sourcefiles) $(sourcei
# Build a list of dependencies for each parsed file.
$(BUILDDIR)/dependencies : $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $(xmlfiles)) $(THISDIR)/lib/catalog2dependencies.xslt
@$(ECHO) "Identifying dependencies…"
- $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2dependencies.xslt) $(call quote,$<)
+ $(silent)$(TOUCH) $(call quote,$@)
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2dependencies.xslt) $(call quote,$<)
# Generate a catalog of destinations for files.
#
@@ -528,7 +593,8 @@ $(BUILDDIR)/dependencies : $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $
# It does not require knowing the dependencies.
$(BUILDDIR)/destinations : $(BUILDDIR)/catalog $(call parsed,$(filter-out $(assetfiles),$(sourcefiles))) $(THISDIR)/lib/catalog2destinations.xslt
@$(ECHO) "Identifying output destinations…"
- $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2destinations.xslt) $(call quote,$<)
+ $(silent)$(TOUCH) $(call quote,$@)
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2destinations.xslt) $(call quote,$<)
# Generate the main transform.
$(BUILDDIR)/transform.catalog : $(call diffprereqs,transforms,$(sort $(TRANSFORMS)))
@@ -537,20 +603,86 @@ $(BUILDDIR)/transform.catalog : $(call diffprereqs,transforms,$(sort $(TRANSFORM
$(foreach transform,$(TRANSFORMS),$(silent)( $(call id,$(transform)) ) | $(XARGS) -I %% $(XMLCATALOG) --add uri %% $(call quote,$(call fileuri,$(transform))) --noout $(call quote,$@)$(newline))
$(BUILDDIR)/transform.xslt : $(BUILDDIR)/transform.catalog $(THISDIR)/lib/catalog2transform.xslt
@$(ECHO) "Generating main transform…"
- $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2transform.xslt) $(call quote,$<)
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2transform.xslt) $(call quote,$<)
-# Generate the output files using the dependencies as necessary.
-$(call compiled,$(compilablefiles)) : $(BUILDDIR)/public/% : $$(call parsed,$$(call uncompiled,$$@)) $(BUILDDIR)/transform.xslt $$(call parsed,$$(call dependencies,$$(call uncompiled,$$@)))
- $(silent)$(call ensuredirectory,$(dir $@))
- @$(PRINTF) '%s\n' $(call quote,Compiling $*>…)
- $(silent)$(XSLTPROC) -o $(call quote,$@) --stringparam CATALOG 'catalog' --stringparam BUILDTIME $$(TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ') --stringparam SRCTIME $$(TZ= $(STAT) -f '%Sm' -t '%Y-%m-%dT%H:%M:%SZ' $(call quote,$(call uncompiled,$@))) --stringparam IDENTIFIER $(call quote,$(call localuri,$(call uncompiled,$@))) --stringparam PATH $(call quote,/$*) --stringparam CKSUM $$($(CKSUM) $(call quote,$(call uncompiled,$@)) | $(SED) 's/[ ].*//')$(if $(THISREV), --stringparam THISREV $(call quote,$(THISREV)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/transform.xslt) $(call quote,$<)
-$(call compiled,$(filter $(assetfiles),$(sourcefiles))) : $(BUILDDIR)/public/% : $$(call uncompiled,$$@)
+# Compile the result files using the dependencies as necessary.
+$(call compiled,$(compilablefiles)) : $(BUILDDIR)/results/% : $$(call parsed,$$(call uncompiled,$$@)) $(BUILDDIR)/transform.xslt $$(call parsed,$$(call dependencies,$$(call uncompiled,$$@)))
@$(PRINTF) '%s\n' $(call quote,Compiling $*>…)
$(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) --stringparam CATALOG 'catalog' --stringparam BUILDTIME $$(TZ= $(DATE) '+%Y-%m-%dT%H:%M:%SZ') --stringparam SRCTIME '$(call modtime,$(call uncompiled,$@))' --stringparam IDENTIFIER $(call quote,$(call localuri,$(call uncompiled,$@))) --stringparam PATH $(call quote,/$*) --stringparam CKSUM $$($(CKSUM) $(call quote,$(call uncompiled,$@)) | $(SED) 's/[ ].*//')$(if $(THISREV), --stringparam THISREV $(call quote,$(THISREV)),)$(if $(SRCREV), --stringparam SRCREV $(call quote,$(SRCREV)),) $(call quote,$(BUILDDIR)/transform.xslt) $(call quote,$<)
+
+# Create the final files from the compiled results (or error in the case of recursive ones).
+$(call built,$(compilablefiles)) : $(BUILDDIR)/public/% : $(BUILDDIR)/results/%
+ @$(PRINTF) '%s\n' $(call quote,Building $*>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(call processresultto,$<,$@,$*)
+$(call built,$(filter $(assetfiles),$(sourcefiles))) : $(BUILDDIR)/public/% : $$(call unbuilt,$$@)
+ @$(PRINTF) '%s\n' $(call quote,Building $*>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
$(silent)$(CP) $(call quote,$<) $(call quote,$@)
# Install compiled files (or error in the case of recursive ones).
-$(call installed,$(filter $(assetfiles),$(sourcefiles)) $(recursivefiles) $(compilablefiles)) : $(DESTDIR)/% : $(BUILDDIR)/public/%
+$(call installed,$(recursivefiles) $(installablefiles)) : $(DESTDIR)/% : $(BUILDDIR)/public/%
@$(PRINTF) '%s\n' $(call quote,Installing $*>…)
$(silent)$(call ensuredirectory,$(dir $@))
$(silent)$(CP) $(call quote,$<) $(call quote,$@)
+
+# ━ § BEGIN ARCHIVE MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+else ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:archive)
+
+# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
+
+# Get the list of files in the archive.
+ifneq ($(wildcard $(BUILDDIR)/index),)
+override archivefiles := $(patsubst ./extracted/%,%,$(shell $(CAT) $(call quote,$(BUILDDIR)/index)))
+endif
+
+# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
+
+# ─ ¶ Phony Targets ───────────────────────────────────────────────────
+
+# ─ ¶ Special Targets ─────────────────────────────────────────────────
+
+# Reload this make·file if the archive components have changed.
+$(THISDIR)/GNUmakefile : $(BUILDDIR)/index
+ $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
+ @$(PRINTF) '%b\n' $(call quote,\0033[1mArchive components for $(NAME)> updated. Restarting…\0033[22m)
+
+# ─ ¶ Build Targets ───────────────────────────────────────────────────
+
+# Build the extractor transform for extracting the contents of the archive source file.
+$(BUILDDIR)/extractor.xslt : $(SRC) $(THISDIR)/lib/archive2extractor.xslt
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) $(call quote,$(THISDIR)/lib/archive2extractor.xslt) $(call quote,$<)
+
+# Use the extractor transform to extract the archive source file out into its components.
+#
+# This target sleeps for 1 second to ensure the resulting index will always be newer than this make·file.
+$(BUILDDIR)/index : $(BUILDDIR)/extractor.xslt $(SRC)
+ @$(PRINTF) '%s\n' $(call quote,Extracting components for $(NAME)>…)
+ $(silent)$(SLEEP) 1
+ $(silent)$(XSLTPROC) --nonet --novalid -o $(call quote,$@) --writesubtree $(call quote,$(dir $@)) $(call quote,$<) $(call quote,$(SRC))
+
+# All archive components are extracted alongside the index.
+$(foreach file,$(archivefiles),$(BUILDDIR)/extracted/$(file)) : $(BUILDDIR)/index ;
+
+# Process each extractor component into its final form.
+$(foreach file,$(archivefiles),$(BUILDDIR)/files/$(file)) : $(BUILDDIR)/files/% : $(BUILDDIR)/extracted/%
+ @$(PRINTF) '%s\n' $(call quote,Building $(NAME)/$*>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)$(call processresultto,$<,$@,$*)
+
+# Archive all components in the file to the destination.
+#
+# This is a match‐anything target, with the assumption that this make·file is being called recursively from the default mode.
+$(DESTDIR)/% : $(foreach file,$(archivefiles),$(BUILDDIR)/files/$(file))
+ @$(PRINTF) '%s\n' $(call quote,Archiving $(NAME)>…)
+ $(silent)$(call ensuredirectory,$(dir $@))
+ $(silent)cd $(call quote,$(BUILDDIR)/files); $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -w > $(call quote,$(abspath $@))
+
+# ━ § END DEFINED MAKE·FILE MODES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+else
+$(error Unrecognized MODE: $(MODE))
+endif