]> Lady’s Gitweb - Shushe/blobdiff - GNUmakefile
Allow injecting raw output in X·M·L serialization
[Shushe] / GNUmakefile
index c953ac738ef80071cc89466df708c60707c72e86..944f6dec4d1f877ce6ad4e47bdd8a1de6f05185a 100644 (file)
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2023, 2024 Lady <https://www.ladys.computer/about/#lady>
+# SPDX-License-Identifier: MPL-2.0
+
 SHELL = /bin/sh
 
 # ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -27,6 +30,7 @@ override define makefileinfo
 ║│ • mkdir (requires support for `-p´)                        │║
 ║│ • mv                                                       │║
 ║│ • od (requires support for `-t x1´)                        │║
+║│ • pax (only when generating archives)                      │║
 ║│ • printf                                                   │║
 ║│ • rm                                                       │║
 ║│ • sed                                                      │║
@@ -36,6 +40,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)                            │║
@@ -101,6 +106,7 @@ LN := ln
 MKDIR := mkdir
 MV := mv
 OD := od
+PAX := pax
 PRINTF := printf
 RM := rm
 SED := sed
@@ -109,12 +115,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.
@@ -129,14 +146,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´.
@@ -180,13 +203,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 ─────────────────────────────────
 
@@ -211,6 +246,26 @@ override quote = '$(subst ','"'"',$1)'
 # Outputs an `@´ to silence rules, unless `VERBOSE´ is nonempty.
 override silent := $(if $(VERBOSE),,@)
 
+# ─ ¶ 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' '<transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0"><output method="text" encoding="UTF-8"/></transform>' | $(XSLTPROC) - $(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 ' | $(UUDECODE) -m -r > $(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.
@@ -363,28 +418,31 @@ 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))))
 
 # (callable) Sanitize and wrap the provided plaintext file in X·M·L, printing to `stdout´.
-override wrapplaintext = $(PRINTF) '%s\n' "$$($(PRINTF) '%b' '<?xml version=\042\061.0\042?>\n<script xmlns=\042http://www.w3.org/1999/xhtml\042 type=\042$(call typeoffile,$1)\042><![CDATA['; $(CAT) $(call quote,$1) | $(TR) '\000\013\014' '\032\011\012' | $(SED) $$($(PRINTF) '%s%b' 's/]]>/]]]]><!\[CDATA\[>/g;s/\xEF\xBF\xBE/�/g;s/\xEF\xBF\xBF/�/g;$$!s/\r$$//g;s/\r/\n/g;$$!s/\xC2\x85$$//g;s/\xC2\x85/\n/g;s/\xE2\x80\xA8/\n/g;' 's/[\0001-\0010]/�/g;s/[\0016-\0037]/�/g'); $(PRINTF) '%s' ']]></script>')"
+override wrapplaintext = $(TR) '\000\013\014' '\032\011\012' < $(call quote,$1) | $(SED) "$$($(PRINTF) '%b' 's/]]>/]]]]><!\\[CDATA\\[>/g\ns/\0357\0277\0276/�/g\ns/\0357\0277\0277/�/g\n$$!s/\\r$$//g\ns/\\r/\\n/g\n$$!s/\0302\0205$$//g\ns/\0302\0205/\\n/g;s/\0342\0200\0250/\\n/g;s/[\0001-\0010]/�/g;s/[\0016-\0037]/�/g')" | $(XARGS) -0 -J %% $(PRINTF) '%b%s%s\n' '<?xml version=\042\061.0\042?>\n<script xmlns=\042http://www.w3.org/1999/xhtml\042 type=\042$(call typeoffile,$1)\042><![CDATA[' %% ']]></script>'
 
 # (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´.
 #
@@ -396,7 +454,7 @@ override serializexml = $(SED) "$$($(PRINTF) '%b' '/<?xml[ \t]\\{1,\\}version=[\
 # ─ ¶ Phony Targets ───────────────────────────────────────────────────
 
 # Compile all files, or error if any are recursive.
-all : $(call compiled,$(recursivefiles) $(compilablefiles)) ;
+all : $(call built,$(recursivefiles) $(installablefiles)) ;
 
 # Destroy buildfiles.
 clean :
@@ -421,8 +479,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 : ;
@@ -436,7 +494,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.
@@ -536,18 +594,84 @@ $(BUILDDIR)/transform.xslt : $(BUILDDIR)/transform.catalog $(THISDIR)/lib/catalo
        @$(ECHO) "Generating main transform…"
        $(silent)$(XSLTPROC) -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 $@))
+# 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) -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,$$@)
-       @$(PRINTF) '%s\n' $(call quote,Compiling </$*>…)
+
+# 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) -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) -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
This page took 0.028697 seconds and 4 git commands to generate.