X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/d64d93fe12371f87cc42d72e7e874115b28fb252..c0ddb08dd954a1c0413db9f4067b494532eaf3fe:/GNUmakefile?ds=sidebyside diff --git a/GNUmakefile b/GNUmakefile index f0c9e8a..0a76e49 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2023, 2024 Lady +# SPDX-License-Identifier: MPL-2.0 + SHELL = /bin/sh # ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -16,27 +19,30 @@ override define makefileinfo ║│ │║ ║│ • awk │║ ║│ • cat │║ +║│ • cd │║ ║│ • cksum │║ ║│ • cp │║ ║│ • date │║ -║│ • echo │║ +║│ • diff │║ ║│ • file │║ ║│ • find │║ +║│ • grep │║ ║│ • git (optional) │║ ║│ • ln │║ -║│ • mkdir (requires support for `-p´) │║ +║│ • mkdir │║ ║│ • mv │║ -║│ • od (requires support for `-t x1´) │║ +║│ • od │║ +║│ • pax (only 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) │║ @@ -62,6 +68,10 @@ 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´. │║ ║│ │║ @@ -90,31 +100,44 @@ endef # 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. @@ -126,17 +149,23 @@ SRCDIR := sources # 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 +INCLUDEDIR := $(SRCDIR)/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´. @@ -144,8 +173,8 @@ THISDIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) # By default, `find´ will ignore files which begin with a period and those which are likely to cause problems for `make´. EXTRAFINDRULES := EXTRAFINDINCLUDERULES := -FINDRULES := ! '(' '(' -name '[.-]*' -o -name '*[][*?:|$$%\#\\; ]*' -o -name '*[)]' ')' -a -prune ')'$(if $(EXTRAFINDRULES), -a $(EXTRAFINDRULES),) -FINDINCLUDERULES := $(FINDRULES)$(if $(EXTRAFINDINCLUDERULES), -a $(EXTRAFINDINCLUDERULES),) +FINDRULES := ! '(' '(' -name '[.-]*' -a ! -name '.' -o -name '*[][*?:|$$%\#\\; ]*' -o -name '*[)]' ')' -a -prune ')'$(if $(EXTRAFINDRULES), -a '(' $(EXTRAFINDRULES) ')',) +FINDINCLUDERULES := $(FINDRULES)$(if $(EXTRAFINDINCLUDERULES), -a '(' $(EXTRAFINDINCLUDERULES) ')',) # The list of magic files to use when determining media types. # @@ -166,27 +195,54 @@ EXTRATRANSFORMS := TRANSFORMS := $(sort $(patsubst ./%,%,$(wildcard $(THISDIR)/transforms/*.xslt)) $(EXTRATRANSFORMS)) # List of types which should be treated as X·M·L. -XMLTYPES := application/xml text/xml +# +# Types which start with a `+´ are suffixes and match any type which +# ends accordingly. +XMLTYPES := application/xml text/xml +xml + +# A program to run on basic X·M·L files after they are transformed. +FINALIZE := $(XMLLINT) --nonet --nsclean ifdef GIT ifneq ($(wildcard $(THISDIR)/.git),) # A description of the current git revision of ⛩️📰 书社. -THISREV := $(shell cd $(THISDIR); $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true) +THISREV := $(shell $(CD) $(THISDIR); $(GIT) describe 2>>/dev/null || $(GIT) rev-parse HEAD 2>>/dev/null || :) endif ifneq ($(wildcard .git),) # A description of the current git revision of the working directory. -SRCREV := $(shell $(GIT) describe 2> /dev/null || $(GIT) rev-parse HEAD 2> /dev/null || true) +SRCREV := $(shell $(GIT) describe 2>>/dev/null || $(GIT) rev-parse HEAD 2>>/dev/null || :) 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 silence informative messages. +QUIET := + # 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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Flags for use when calling the shell. +# +# `-e` enables early exit from invocations of multiple commands after +# the first failure. +.SHELLFLAGS := -c -e + +# ━ § BEGIN SHARED MAKE·FILE CONSTRUCTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ─ ¶ Non‐Recipe Variable Definitions ───────────────────────────────── @@ -208,9 +264,50 @@ override comma := , # (callable) Quote the given string for use within shell calls. override quote = '$(subst ','"'"',$1)' -# Outputs an `@´ to silence rules, unless `VERBOSE´ is nonempty. +# (callable) Get the modified time of the provided file. +# +# This touches a file containing only a newline and then diffs it with `/dev/null´ and extracts the timestamp from the diff. +# Interestingly, on Macintosh, the format of `diff -u´ is only Posixy (including a fractional component and timezone) when `COMMAND_MODE=legacy´; however, the timezone will always be zero and the fractional component is ignorable, so it’s not necessary to worry about that here. +# +# The diff will always have an exit status of 1, but this is ignored by piping into Sed. +# +# ☡ This variable creates a subshell every time it is computed. +override modtime = $(shell if $(TEST) ! -f $(call quote,$(BUILDDIR)/.mtime); then $(PRINTF) '%b' '\n' > $(call quote,$(BUILDDIR)/.mtime); fi; $(TOUCH) -r $(call quote,$1) $(call quote,$(BUILDDIR)/.mtime); TZ=UTC0 $(DIFF) -u $(call quote,$(BUILDDIR)/.mtime) /dev/null | $(SED) '1!d;s/.* \([^ ]*\) \([^ ]*\).*$$/\1T\2Z/') + +# ─ ¶ Recipe Variable Definitions ───────────────────────────────────── + +# Outputs an `@´ to silence rules, unless `VERBOSE´ is non·empty. override silent := $(if $(VERBOSE),,@) +# (callable) Only perform the rule if `QUIET` is not set. +override inform = $(if $(QUIET),,@$1) + +# (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 + +# Quote standard input in such a way that piping it to xargs will result in it being processed as a single argument. +# +# If standard input ends in a newline, it is stripped; all other newlines are preserved. +override xargsquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/;$$!s/$$/\\/) + +# Quote standard input in such a way that piping it to xargs will result in each line being processed as a single argument. +override xargsmultiquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/) + +# (callable) Test if the provided xpath expression matches the provided document. +override xpath = $(XMLLINT) --noent --nonet --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 $(call extracttext,$1) >|$(call quote,$2); elif $(call xpath,/*[local-name()="base64-binary" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$1); then { $(PRINTF) '%s\n' 'begin-base64 644 -'; $(call extracttext,$1) | $(TR) -d '\t\n\f\r '; $(PRINTF) '\n%s\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 $(MAKE) -f $(call quote,$(abspath $(THISDIR)/GNUmakefile)) 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' $(call quote,$2); else $(FINALIZE) $(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. @@ -218,13 +315,13 @@ override silent := $(if $(VERBOSE),,@) # Calling this variable is useful when a given target should be updated whenever its list of prerequisites changes in addition to whenever there is a change to one of its prerequisites. # # ☡ This variable creates at least one subshell every time it is computed. -override diffprereqs = $(if $(subst $(shell $(CAT) $(call quote,$(BUILDDIR)/lastprereqs/$1) 2> /dev/null || true),,$2),$2 FORCE$(and $(shell $(call ensuredirectory,$(BUILDDIR)/lastprereqs) && $(PRINTF) '%s\n' $(call quote,$2) > $(BUILDDIR)/lastprereqs/$1),),$2) +override diffprereqs = $(if $(subst $(shell $(CAT) $(call quote,$(BUILDDIR)/lastprereqs/$1) 2>>/dev/null || :),,$2),$2 FORCE$(and $(shell $(call ensuredirectory,$(BUILDDIR)/lastprereqs) && $(PRINTF) '%s\n' $(call quote,$2) >|$(BUILDDIR)/lastprereqs/$1),),$2) # (callable) Escape special characters for use in sed regular expressions. override sedesc = $(subst /,[/],$(subst $$,\$$,$(subst *,\*,$(subst .,\.,$(subst [,\[,$(subst ^,\^,$(subst \,\\,$1))))))) # The command to use for percent‐decoding. -override perdeccmd := $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0-9A-Fa-f]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0-9A-Fa-f]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf "%s",$$0}/%/{d="0123456789ABCDEF";v=substr(toupper($$0),2,2);printf "\\%04o",(index(d,substr(v,1,1))-1)*16+index(d,substr(v,2,1))-1}' | $(XARGS) -0 $(PRINTF) '%b' +override perdeccmd := $(SED) 's/|/%7C/g;s/[\]/%5C/g;s/%[0123456789ABCDEFabcdef]\{2\}/|&|/g' | $(TR) '|' '\n' | $(SED) '/^%[0123456789ABCDEFabcdef]\{2\}$$/!s/%/|%25|/' | $(TR) '|' '\n' | $(AWK) '$$0!~/%/{printf "%s",$$0}/%/{d="0123456789ABCDEF";v=substr(toupper($$0),2,2);printf "\\%04o",(index(d,substr(v,1,1))-1)*16+index(d,substr(v,2,1))-1}' | $(xargsquote) | $(XARGS) -E '' $(PRINTF) '%b' # (callable) Percent‐decode the given strings. # @@ -239,7 +336,7 @@ override perdec = $(shell $(PRINTF) '%s\0450A' $(foreach encoded,$1,$(call quote # It is assumed that the given strings do not contain newlines. # # ☡ This variable creates a subshell every time it is computed. -override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$(unencoded))) | $(OD) -t x1 | $(SED) 's/^[0-9]*//;s/0[Aa]/%&/g;s/2[146-9A-Ea-e]/%&/g;s/3[0-9AaBbDd]/%&/g;s/40/%&/g;s/[46][1-9A-Fa-f]/%&/g;s/[57][0-9Aa]/%&/g;s/5[Ff]/%&/g;s/7[Ee]/%&/g;s/[ ][0-9A-Fa-f]\{2\}/%25&/g' | $(TR) -d ' \n' | $(TR) 'abcdef' 'ABCDEF' | $(perdeccmd)) +override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$(unencoded))) | $(OD) -t x1 | $(SED) 's/^[0123456789]*//;s/0[Aa]/%&/g;s/2[146789ABCDEabcde]/%&/g;s/3[0123456789AaBbDd]/%&/g;s/40/%&/g;s/[46][123456789ABCDEFabcdef]/%&/g;s/[57][0123456789Aa]/%&/g;s/5[Ff]/%&/g;s/7[Ee]/%&/g;s/[ ][0123456789ABCDEFabcdef]\{2\}/%25&/g' | $(TR) -d ' \n' | $(TR) 'abcdef' 'ABCDEF' | $(perdeccmd)) # (callable) Percent‐encode each component in the given paths. # @@ -247,23 +344,25 @@ override perenc = $(shell $(PRINTF) '%s\n' $(foreach unencoded,$1,$(call quote,$ override pathenc = $(subst %2F,/,$(call perenc,$1)) # (overridable) Collect all of the applicable includes from the includes directory. -sourceincludes := $(shell $(FIND) $(foreach dir,$(INCLUDEDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f) +sourceincludes := $(if $(and $(INCLUDEDIR),$(wildcard $(INCLUDEDIR))),$(patsubst ./%,%,$(shell $(FIND) $(foreach dir,$(INCLUDEDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f)),) # (overridable) Collect all of the applicable source files from the source directory, removing any which are also includes. -sourcefiles := $(filter-out $(sourceincludes),$(shell $(FIND) $(foreach dir,$(SRCDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f)) +sourcefiles := $(if $(and $(SRCDIR),$(wildcard $(SRCDIR))),$(filter-out $(sourceincludes),$(patsubst ./%,%,$(shell $(FIND) $(foreach dir,$(SRCDIR),$(call quote,$(dir))) '(' $(FINDRULES) ')' -a -type f)))) # 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. ifneq ($(wildcard $(BUILDDIR)/parser.xslt),) -override plaintexttypes := $(filter-out $(XMLTYPES),$(shell $(XMLLINT) --xpath '/*/*[@name="书社:parsers"]//*[namespace-uri()="http://www.w3.org/1999/xhtml" and local-name()="dd"]/text()' $(call quote,$(BUILDDIR)/parser.xslt))) +override plaintexttypes := $(filter-out $(XMLTYPES),$(shell $(XMLLINT) --noent --nonet --xpath '/*/*[@name="书社:parsers"]//*[namespace-uri()="http://www.w3.org/1999/xhtml" and local-name()="dd"]/text()' $(call quote,$(BUILDDIR)/parser.xslt))) endif -# (callable) Get all of the files (source and includes) which have the given types. -override filesoftype = $(foreach type,$1,$(patsubst %|$(type),%,$(filter %|$(type),$(types)))) +# (callable) Get all of the files (source and includes) which have the provided types. +# +# If a provided type begins with a `+´, it is analysed as a suffix, and all matching types will be used. +override filesoftype = $(foreach typeorsuffix,$1,$(foreach type,$(if $(filter +%,$(typeorsuffix)),$(sort $(foreach suffixedtype,$(filter %$(typeorsuffix),$(types)),$(lastword $(subst |, ,$(suffixedtype))))),$(typeorsuffix)),$(patsubst %|$(type),%,$(filter %|$(type),$(types))))) # Build up collections of various file types. override xmlfiles := $(call filesoftype,$(XMLTYPES)) @@ -271,7 +370,7 @@ override plaintextfiles := $(filter-out $(xmlfiles),$(call filesoftype,$(plainte override assetfiles := $(filter-out $(xmlfiles) $(plaintextfiles),$(sourcefiles) $(sourceincludes)) # (callable) Get the types of the given files. -override typeoffile = $(patsubst $(foreach file,$1,$(file)|%),%,$(filter $(foreach file,$1,$(file)|%),$(types))) +override typeoffile = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(types))),$(error Unable to get type of file `$(file)´))) # Pair each source magic file with its location in the build directory. override magicpair := $(foreach magicfile,$(MAGIC),$(magicfile)|$(BUILDDIR)/magic/$(notdir $(magicfile))) @@ -283,15 +382,15 @@ override magicsource = $(foreach magicpath,$1,$(patsubst %|$(magicpath),%,$(firs override magicfile = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(magicpair)))) # (callable) Get the local path for the given source file. -override sourcepath = $(firstword $(foreach directory,$(SRCDIR),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),))) +override sourcepath = $(or $(firstword $(foreach directory,$(SRCDIR),$(if $(filter .,$(directory)),$(wildcard $1),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))),$(error Unable to get local path for source file `$1´)) # (callable) Get the local path for the given include. -override includepath = $(firstword $(foreach directory,$(INCLUDEDIR),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),))) +override includepath = $(or $(firstword $(foreach directory,$(INCLUDEDIR),$(if $(filter .,$(directory)),$(wildcard $1),$(if $(filter $(directory)/%,$1),$(patsubst $(directory)/%,%,$1),)))),$(error Unable to get local path for include file `$1´)) # (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))))) @@ -302,11 +401,11 @@ override localuri = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%, # (callable) Get the source files for the given local uris. override sourcefile = $(foreach local,$1,$(patsubst %|$(local),%,$(filter %|$(local),$(sourcelocalpair)))) -# Adds a requirement on `$(BUILDDIR)/.update-types´ if the file is present. +# Resolves to the `FORCE´ target if `$(BUILDDIR)/.update-types´ is present. # -# This file is created after a reload due to type changes, and is removed after. -# Requiring it ensures that file classifications are up·to·date immediately after the reload. -override typeupdates := $(wildcard $(BUILDDIR)/.update-types) +# This file is created before a reload due to type changes, and is removed after. +# This ensures that file classifications are up·to·date immediately after the reload. +override typeupdates := $(if $(wildcard $(BUILDDIR)/.update-types),FORCE,) # Pair each source file and include with its parsed location. override sourceparsedpair := $(foreach file,$(sourcefiles) $(sourceincludes),$(file)|$(BUILDDIR)/$(if $(filter $(file),$(sourceincludes)),includes/$(call includepath,$(file)),sources/$(call sourcepath,$(file)))) @@ -321,7 +420,7 @@ override unparsed = $(foreach file,$1,$(patsubst %|$(file),%,$(filter %|$(file), override fileuripairs := $(join $(patsubst %,%|,$(PARSERS) $(TRANSFORMS) $(call parsed,$(sourcefiles) $(sourceincludes))),$(call pathenc,$(foreach uriable,$(PARSERS) $(TRANSFORMS) $(call parsed,$(sourcefiles) $(sourceincludes)),file://$(abspath $(uriable))))) # (callable) Get the file u·r·is for the given parsers, transforms, or parsed files. -override fileuri = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(fileuripairs)))) +override fileuri = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(fileuripairs))),$(error Unable to get file u·r·i for `$(file)´))) ifneq ($(wildcard $(BUILDDIR)/dependencies),) # Pair each file with a list of dependencies for it. @@ -333,9 +432,13 @@ override dependenciesforfile := $(foreach file,$(filter-out $(assetfiles),$(sour override dependencyuris = $(foreach file,$1,$(subst |, ,$(patsubst $(file)|%,%,$(filter $(file)|%,$(dependenciesforfile))))) # (callable) Get the list of recursive dependencies for the given source files. +# +# If the file cannot have dependencies (e·g is an asset file), the resulting value will be the empty string. override recursives = $(foreach uri,$(filter -%,$(call dependencyuris,$1)),$(call sourcefile,$(patsubst -%,%,$(uri)))) # (callable) Get the list of (nonrecursive) dependencies for the given source files. +# +# If the file cannot have dependencies (e·g is an asset file), the resulting value will be the empty string. override dependencies = $(foreach uri,$(filter-out -%,$(call dependencyuris,$1)),$(call sourcefile,$(uri))) endif @@ -359,41 +462,53 @@ override destinations := $(shell $(CAT) $(BUILDDIR)/destinations) # Pair source files and their destinations. override sourcedestinationpair := $(foreach destination,$(destinations),$(call sourcefile,$(firstword $(subst |, ,$(destination))))|$(call perdec,$(subst $(space),|,$(wordlist 2,$(words $(subst |, ,$(destination))),$(subst |, ,$(destination)))))) -# (callable) Get the destination for the given source files. -override destination = $(foreach file,$1,$(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcedestinationpair)))) +# (callable) Get the destination for the given source files, or return `NOTDEF´. +# +# The fallback here is because destinations are used to generate targets and thus must always be non·empty, even when they haven’t been generated yet. +override destination = $(foreach file,$1,$(or $(patsubst $(file)|%,%,$(filter $(file)|%,$(sourcedestinationpair))),NOTDEF)) # 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)))) +override id = $(XMLLINT) --noent --nonet --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' '\n')" +override wrapplaintext = { $(PRINTF) '%s\n%s' '' ''; } + +# (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}')" # ─ ¶ Phony Targets ─────────────────────────────────────────────────── # Compile all files, or error if any are recursive. -all : $(call compiled,$(recursivefiles) $(compilablefiles)) ; +all : $(call built,$(recursivefiles) $(installablefiles)) ; # Destroy buildfiles. clean : - $(if $(BUILDDIR),$(silent)$(RM) -rf $(call quote,$(BUILDDIR)/),) + $(if $(BUILDDIR),$(silent)$(RM) -f -R $(call quote,$(BUILDDIR)/),) # Destroy build directory and installed files. gone : clean uninstall ; @@ -407,15 +522,19 @@ install : $(call installed,$(recursivefiles) $(installablefiles)) ; # List all source files and includes and their computed types. 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 ' + @$(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] Modified:|$(call modtime,$(file))$(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 ' + +# 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 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))))) >&2 && exit 1 # Add as a prerequisite to treat the target as tho it were phony. FORCE : ; @@ -428,8 +547,11 @@ FORCE : ; # Don’t use any implicit rules. .SUFFIXES : ; +# Don’t delete these files even if Make is stopped in the process of rebuilding them. +.PRECIOUS : GNUmakefile ; + # 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 listout 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. @@ -442,10 +564,10 @@ ifneq ($(typeupdates)$(wildcard $(BUILDDIR)/dependencies)$(wildcard $(BUILDDIR)/ $(THISDIR)/GNUmakefile :: $(BUILDDIR)/dependencies $(BUILDDIR)/destinations $(silent)$(TOUCH) $(THISDIR)/GNUmakefile ifeq ($(typeupdates),) - @if $(TEST) ! -f $(call quote,$(BUILDDIR)/.update-types); then $(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m'; fi + $(call inform,if $(TEST) ! -f $(call quote,$(BUILDDIR)/.update-types); then $(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' >&2; fi) else - $(silent)$(RM) $(BUILDDIR)/.update-types - @$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' + $(silent)$(RM) $(call quote,$(BUILDDIR)/.update-types) + $(call inform,$(PRINTF) '%b\n' '\0033[1mDependency graph and output destinations updated. Restarting…\0033[22m' >&2) endif endif @@ -458,7 +580,7 @@ ifeq ($(typeupdates),) $(THISDIR)/GNUmakefile :: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt $(silent)$(TOUCH) $(THISDIR)/GNUmakefile $(silent)$(RM) -f $(call quote,$(BUILDDIR)/dependencies) $(call quote,$(BUILDDIR)/destinations) - @$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m' + $(call inform,$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m' >&2) $(silent)$(SLEEP) 1 $(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types) endif @@ -475,19 +597,19 @@ $(call magicfile,$(MAGIC)) : $(BUILDDIR)/magic/% : $$(call magicsource,$$@) # It must be updated if any of the files in the magic directory change. $(BUILDDIR)/magic.mgc : $(call diffprereqs,magic,$(sort $(call magicfile,$(MAGIC)))) $(foreach outdated,$(filter-out $^,$(wildcard $(BUILDDIR)/magic/*)),$(silent)$(RM) $(call quote,$(outdated))$(newline)) - @$(ECHO) "Compiling new magic…" + $(call inform,$(PRINTF) '%s\n' 'Compiling new magic…' >&2) $(silent)$(call ensuredirectory,$(dir $@)) - $(silent)cd $(call quote,$(BUILDDIR)) && $(FILE) -C -m $(call quote,$(realpath $(BUILDDIR)/magic)) + $(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 : $(call diffprereqs,parsers,$(sort $(PARSERS))) - @$(ECHO) "Generating catalog of parsers…" + $(call inform,$(PRINTF) '%s\n' 'Generating catalog of parsers…' >&2) $(silent)$(XMLCATALOG) --create --noout $(call quote,$@) - $(foreach parser,$(PARSERS),$(silent)( $(call id,$(parser)) ) | $(XARGS) -I %% $(XMLCATALOG) --add uri %% $(call quote,$(call fileuri,$(parser))) --noout $(call quote,$@)$(newline)) + $(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 - @$(ECHO) "Generating main parser…" - $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<) + $(call inform,$(PRINTF) '%s\n' 'Generating main parser…' >&2) + $(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. @@ -495,52 +617,125 @@ $(BUILDDIR)/parser.xslt : $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2pars # Even plain X·M·L files are parsed, because they may contain X·H·T·M·L `