X-Git-Url: https://git.ladys.computer/Shushe/blobdiff_plain/61599492d0451c48f963a13a78a11a59415eb6f0..5df1b342a607db60d876eeb10d953b7a45464e24:/GNUmakefile?ds=sidebyside diff --git a/GNUmakefile b/GNUmakefile index 2c77723..89ca784 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -19,15 +19,16 @@ override define makefileinfo ║│ │║ ║│ • awk │║ ║│ • cat │║ +║│ • cd │║ ║│ • cksum │║ ║│ • cp │║ ║│ • date │║ +║│ • diff │║ ║│ • file │║ ║│ • find │║ ║│ • grep │║ ║│ • git (optional) │║ ║│ • ln │║ -║│ • ls │║ ║│ • mkdir │║ ║│ • mv │║ ║│ • od │║ @@ -99,15 +100,16 @@ 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 +DIFF := diff FILE := file FIND := find GIT := git GREP := grep LN := ln -LS := ls MKDIR := mkdir MV := mv OD := od @@ -147,7 +149,7 @@ 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. # @@ -201,12 +203,12 @@ XMLTYPES := application/xml text/xml +xml 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 @@ -231,6 +233,12 @@ VERBOSE := # The default target for this makefile. .DEFAULT_GOAL := all +# 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 ───────────────────────────────── @@ -255,17 +263,13 @@ override quote = '$(subst ','"'"',$1)' # (callable) Get the modified time of the provided file. # -# Only the date·time information available via `ls -l´ is supported; that is, only minute precision for files newer than six months, and only day precision for files which are older. -# This is, unfortunately, the best that can be done in pure Posix as of now. -# -# There is an easier implementation which provides the full date·time by archiving an empty file and then reading it back out of the archive :— +# 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. # -# $(shell LC_TIME=POSIX TZ=UTC0 $(TOUCH) -m -r $(callquote,$1) $(BUILDDIR)/.mtime && $(PAX) -w -x ustar $(BUILDDIR)/.mtime | $(PAX) -v -o 'listopt=%(mtime=%Y-%m-%dT%H:%M:%SZ)T') -# -# However, it depends on the `listopt´ option in `pax´, which (despite being specified in Posix) no implementations actually support. +# 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 { $(DATE) -u '+%Y-%m'; LC_TIME=POSIX TZ=UTC0 ls -q -A -l $(call quote,$1) | $(SED) 's/\([^ ]* \{1,\}\)\{5\}\(\([^ ]* \{1,\}\)\{3\}\).*/\2/;s/ \([0123456789]\) / 0\1 /;s/Jan/01/;s/Feb/02/;s/Mar/03/;s/Apr/04/;s/May/05/;s/Jun/06/;s/Jul/07/;s/Aug/08/;s/Sep/09/;s/Oct/10/;s/Nov/11/;s/Dec/12/;s/\([0123456789]\{2\}\) *\([0123456789]\{2\}\) *\([0123456789]\{4\}\)/\3-\1-\2T00:00:00Z/;s/\([0123456789]\{2\}\) *\([0123456789]\{2\}\) *\([0123456789:]\{5\}\)/XXXX-\1-\2T\3:00Z/'; } | $(TR) -d ' ' | $(AWK) -F '-' 'NR==1{year=$$1;month=$$2}$$1=="XXXX"&&$$2<=month{print year"-"$$2"-"$$3}$$1=="XXXX"&&$$2>month{print year-1"-"$$2"-"$$3}NR>1&&$$1!="XXXX"') +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 ───────────────────────────────────── @@ -287,13 +291,13 @@ override xargsquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/;$$!s/$$/\\/ override xargsmultiquote = $(SED) $(call quote,s/'/'"'"'/g;s/^/'/;s/$$/'/) # (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 +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 $(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)) $(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 $(XMLLINT) --nsclean $(call quote,$1) > $(call quote,$2); fi +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 $(XMLLINT) --nsclean $(call quote,$1) >|$(call quote,$2); fi # ━ § BEGIN DEFAULT MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -308,7 +312,7 @@ ifeq ($(MODE),urn:fdc:ladys.computer:20231231:Shu1She4:mode:default) # 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))))))) @@ -363,7 +367,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,$(patsubst $(file)|%,%,$(filter $(file)|%,$(types)))) # Pair each source magic file with its location in the build directory. override magicpair := $(foreach magicfile,$(MAGIC),$(magicfile)|$(BUILDDIR)/magic/$(notdir $(magicfile))) @@ -394,11 +398,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)))) @@ -476,17 +480,17 @@ endif # ─ ¶ Recipe Variable Definitions ───────────────────────────────────── # (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) --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%s' '' ''; } +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}')" +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 ─────────────────────────────────────────────────── @@ -521,7 +525,7 @@ uninstall : # 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 && false + @$(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 : ; @@ -534,6 +538,9 @@ 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 listout uninstall $(call built,$(recursivefiles)) ; @@ -583,7 +590,7 @@ $(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 $(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. @@ -603,7 +610,7 @@ $(BUILDDIR)/parser.xslt : $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2pars $(call parsed,$(sourcefiles) $(sourceincludes)) : % : $$(call unparsed,$$@) $(BUILDDIR)/parser.xslt $(typeupdates) $(call inform,$(PRINTF) '%s\n' $(call quote,Processing `$<´…) >&2) $(silent)$(call ensuredirectory,$(dir $@)) - $(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 $$($(DATE) -u '+%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) -) + $(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 $$($(DATE) -u '+%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. # @@ -716,7 +723,7 @@ $(DESTDIR)/% : $(SRC) $(foreach file,$(archivefiles),$(BUILDDIR)/files/$(file)) $(call inform,$(PRINTF) '%s\n' $(call quote,Archiving …) >&2) $(silent)$(call ensuredirectory,$(dir $@)) $(silent)$(RM) -f -R $(call quote,$@) - $(silent)cd $(call quote,$(BUILDDIR)/files); if $(call xpath,/*/@*[local-name()="expanded" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$(abspath $<)); then $(MKDIR) -p $(call quote,$(abspath $@)); $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -r -w $(call quote,$(abspath $@)); else $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -w -x ustar > $(call quote,$(abspath $@)); fi + $(silent)$(CD) $(call quote,$(BUILDDIR)/files); if $(call xpath,/*/@*[local-name()="expanded" and namespace-uri()="urn:fdc:ladys.computer:20231231:Shu1She4"],$(abspath $<)); then $(MKDIR) -p $(call quote,$(abspath $@)); $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -r -w $(call quote,$(abspath $@)); else $(PRINTF) '%s\n' $(foreach file,$(archivefiles),$(call quote,$(file))) | $(PAX) -w -x ustar >|$(call quote,$(abspath $@)); fi # ━ § END DEFINED MAKE·FILE MODES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━