Newer
Older
Handbook / make-includes / build_document_rules.make
################################################################################
#
# File: $Id$
#
# Standard variables and rules for building a particular document within a
# section of the handbook (e.g., a single tutorial). Altering these
# definitions will affect ALL DOCUMENT MAKEFILES FOR ALL PAPERS!! If you
# need to do something specific for a particular paper, include a custom
# rule in its makefile. DON'T add it here!
#
################################################################################


################################################################################
#
# Run one-time initialisation stuff, _unless_ NOINIT is defined. Any
# "parent" makefile that's already done the same one-time initialisation
# and calls this one should define NOINIT (the value doesn't matter) to
# switch off one-time initialisation in this makefile. Thus if the
# "parent" makefile calls this makefile several times, things like the
# content checking will only happen once for a single invocation of the
# "parent" makefile.
# 
# When running this makefile standalone, _don't_ define NOINIT, and
# everything will still work as expected.
#
ifndef NOINIT
include $(GLOBAL_HANDBOOK_INCLUDE)/remove_content_marker_files.make
endif


################################################################################
#
# Given that we print out our own messages, I see no point in splurging
# "Entering...leaving directory" messages all over the screen. It's hard
# enough to figure out what's going on already :)
#
MAKEFLAGS=--no-print-directory


################################################################################
#
# Include variables defining the current paper.
#
include $(LOCAL_HANDBOOK_INCLUDE)/paper_variables.make


################################################################################
#
# We make quite a lot of reference to files in the handbook directory,
# so set a variable to point to it. PAPER_ROOT is defined in the include
# file paper_variables.make.
#
HANDBOOK_ROOT:=$(PAPER_ROOT)/Handbook


################################################################################
#
# Include XSLT functions.
#
include $(GLOBAL_HANDBOOK_INCLUDE)/xslt_functions.make


################################################################################
#
# Defines which section we are currently building. This is actually the
# path to the main folder for the section. Normally this is passed in as
# an argument to make from a higher-level makefile (i.e., "make
# SECTION=path/to/section").
#
# Hmm, sh seems to convert uppercase letters in my paths to lowercase.
# I'm not sure whether this will break anything or not...
#
# I _can_ confirm that having spaces anywhere in the parent path causes
# serious breakage, because notdir returns bogus results in such cases.
# All of the make functions assume whitespace delimitation, and there
# doesn't seem to be any obvious way to work around this other than
# eliminating spaces from the parent path (quoting the result of the
# shell function below makes no difference). Fortunately, this is pretty
# easy to do :)
#
#
SECTION?=$(notdir $(shell cd ..; pwd))


################################################################################
#
# Set up the context for the current document.
#
# We use the same base name for both the directory and the document, which
# makes life very easy :)
#
BASE_NAME:=$(notdir $(CURDIR))

#
# SOURCE_XML is the original source XML, from which DERIVED_XML is
# generated (the derived source has the includes for questions, etc.,
# inserted).
#
SOURCE_XML:=$(BASE_NAME).xml
DERIVED_XML:=$(SOURCE_XML:.xml=-derived.xml)

#
# Define the names of the output files.
#
WEB_QUESTIONS_HTML:=$(BASE_NAME)-questions.html
WEB_ANSWERS_HTML:=$(BASE_NAME)-answers.html

PRINT_QUESTIONS_TEX:=$(WEB_QUESTIONS_HTML:.html=.tex)
PRINT_ANSWERS_TEX:=$(WEB_ANSWERS_HTML:.html=.tex)

PRINT_QUESTIONS_PDF_1UP:=$(PRINT_QUESTIONS_TEX:.tex=.pdf)
PRINT_ANSWERS_PDF_1UP:=$(PRINT_ANSWERS_TEX:.tex=.pdf)
PRINT_QUESTIONS_PDF_2UP:=$(PRINT_QUESTIONS_TEX:.tex=-2up.pdf)
PRINT_ANSWERS_PDF_2UP:=$(PRINT_ANSWERS_TEX:.tex=-2up.pdf)

#
# Groupings of various web- and print-related files, for easy reference.
# Feel free to add to these as necessary :)
#
DERIVED_WEB_FILES:=$(WEB_QUESTIONS_HTML) $(WEB_ANSWERS_HTML)
DERIVED_PRINT_FILES:=$(PRINT_QUESTIONS_TEX) $(PRINT_ANSWERS_TEX) \
	$(PRINT_QUESTIONS_PDF_1UP) $(PRINT_ANSWERS_PDF_1UP) \
	$(PRINT_QUESTIONS_PDF_2UP) $(PRINT_ANSWERS_PDF_2UP)


#
# List of XSL stylesheets. If any of these change, we need to rebuild
# everything. 
#
# We can't use the resolver approach under Windows because of the DOS
# pathnames. The style sheets come out of the resolver with paths like
# C:\bar\foo\..., and make interprets the colon as an extra dependency
# delimiter (tested and behaviour verified). Of course, this works fine on
# any platform with sensible path standards. Quoting the value doesn't
# help, unfortunately. Bugger :(
#
#XSLT_STYLESHEETS:=$(shell java org.apache.xml.resolver.apps.resolver -u file:///xml2html.xsl uri | ( grep 'Result: file:' || echo '::xml2html.xsl' ) | cut -d':' -f3-) $(shell java org.apache.xml.resolver.apps.resolver -u file:///xml2latex.xsl uri | ( grep 'Result: file:' || echo '::xml2latex.xsl' ) | cut -d':' -f3-)
XSLT_STYLESHEETS:=$(TEACHING_SHARED)/Authoring/XML/xml2html.xsl $(TEACHING_SHARED)/Authoring/XML/xml2latex.xsl


################################################################################
#
# Extract the list of included filenames from the source template. Hooray
# for the shell function and Perl! Include file paths are delimited by
# <@INC[ ]@>. All paths are relative to $(PAPER_ROOT). We don't use
# $(PAPER_ROOT)/$(SECTION), because some of the include directories may be
# in relatively arbitrary locations.
#
CONTENT_SRC:=$(shell perl -ne 'print "$(PAPER_ROOT)/$$1\n" if /<\@INC\[([^]]+)\]@>/;' $(SOURCE_XML))


################################################################################
#
# Directory to install files into on web server.
#
INSTALL_DIRECTORY:=$(HANDBOOK_INSTALL_ROOT)/$(SUBJECT_CODE)$(PAPER_NUMBER)/www/$(SECTION)/$(BASE_NAME)


################################################################################
#
# Files to be installed on web server.
#
QUESTION_INSTALL_FILES:=$(WEB_QUESTIONS_HTML) $(PRINT_QUESTIONS_PDF_1UP) \
	$(PRINT_QUESTIONS_PDF_2UP) $(wildcard *.png)

ANSWER_INSTALL_FILES:=$(WEB_ANSWERS_HTML) $(PRINT_ANSWERS_PDF_1UP) \
	$(PRINT_ANSWERS_PDF_2UP)


################################################################################
#
# Lists of files for cleaning up. Add to these (using +=) as necessary in
# derived makefiles. Note that ALL_CLEAN_FILES comprises files that need
# to be deleted _in addition_ to those listed in WEB_CLEAN_FILES and
# PRINT_CLEAN_FILES (that is, it's the list of additional files for the
# "clean" target, which depends on "web-clean" and "print-clean" anyway).
#
WEB_CLEAN_FILES:=$(DERIVED_WEB_FILES) *-web*.png
PRINT_CLEAN_FILES:=$(DERIVED_PRINT_FILES) *-print*.png *.pdf
ALL_CLEAN_FILES:=$(DERIVED_XML)


################################################################################
#
# List of possible targets. If you need to add to this for a specific
# case, use TARGETS+=xxx in the actual makefile.
#
TARGETS:=all content targets debug install-questions install-answers \
	web web-questions web-answers \
	print print-questions print-answers \
	questions answers \
	clean web-clean print-clean

.PHONY: $(TARGETS)


################################################################################
#
# Build everything.
#
all: web print


################################################################################
#
# Build questions or answers only.
#
questions: web-questions print-questions

answers: web-answers print-answers


################################################################################
#
# Build web version only.
#
web: web-questions web-answers

web-questions: content $(WEB_QUESTIONS_HTML)

$(WEB_QUESTIONS_HTML): $(DERIVED_XML)
	@announce "Building $@ from $<"
	$(call xslt,$<,xml2html.xsl,$(call xslt_parameter,department,'$(SUBJECT_CODE)'),$(call xslt_parameter,paper,'$(PAPER_NUMBER)'),$(call xslt_parameter,standalone,'no'),$(call xslt_parameter,showanswers,'no'),$(call xslt_parameter,base-path,'.'),$(call xslt_parameter,image-format,'png')) > $@

web-answers: content $(WEB_ANSWERS_HTML)

$(WEB_ANSWERS_HTML): $(DERIVED_XML)
	@announce "Building $@ from $<"
	$(call xslt,$<,xml2html.xsl,$(call xslt_parameter,department,'$(SUBJECT_CODE)'),$(call xslt_parameter,paper,'$(PAPER_NUMBER)'),$(call xslt_parameter,standalone,'no'),$(call xslt_parameter,showanswers,'yes'),$(call xslt_parameter,base-path,'.'),$(call xslt_parameter,image-format,'png')) > $@


################################################################################
#
# Build print version only. We add code to the LaTeX source to mark the
# first and last pages of the document, so that an individual PDF for this
# document can be generated later. \markfirstpage and \marklastpage are
# macros defined in handbook_template.tex that write the current physical
# page number to the .aux file.
#
print: print-questions print-answers

print-questions: content $(PRINT_QUESTIONS_TEX)

$(PRINT_QUESTIONS_TEX): $(DERIVED_XML)
	@announce "Building $@ from $<"
	@echo "\\markfirstpage{$(SECTION)/$(BASE_NAME)/$(@:.tex=.pdf)}" > $@
	$(call xslt,$<,xml2latex.xsl,$(call xslt_parameter,department,'$(SUBJECT_CODE)'),$(call xslt_parameter,paper,'$(PAPER_NUMBER)'),$(call xslt_parameter,standalone,'no'),$(call xslt_parameter,showanswers,'no'),$(call xslt_parameter,base-path,'sections/$(SECTION)/$(BASE_NAME)'),$(call xslt_parameter,image-format,'pdf')) >> $@
	@echo "\\marklastpage{$(SECTION)/$(BASE_NAME)/$(@:.tex=.pdf)}" >> $@

%-questions.pdf: %-questions.tex
	@announce "Generating $@"
	@$(if $(wildcard $(HANDBOOK_ROOT)/handbook.pdf),,$(error $(HANDBOOK_ROOT)/handbook.pdf is missing))
	@gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=$@ \
		-dFirstPage=$(shell grep '$@.1up.first' $(HANDBOOK_ROOT)/handbook.aux | cut -d= -f2) \
		-dLastPage=$(shell grep '$@.1up.last' $(HANDBOOK_ROOT)/handbook.aux | cut -d= -f2) $(HANDBOOK_ROOT)/handbook.pdf

%-questions-2up.pdf: %-questions.tex
	@announce "Generating $@"
	@$(if $(wildcard $(HANDBOOK_ROOT)/handbook-2up.pdf),,$(error $(HANDBOOK_ROOT)/handbook-2up.pdf is missing))
	@gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=$@ \
		-dFirstPage=$(shell grep '$(@:-2up.pdf=.pdf).2up.first' $(HANDBOOK_ROOT)/handbook.aux | cut -d= -f2) \
		-dLastPage=$(shell grep '$(@:-2up.pdf=.pdf).2up.last' $(HANDBOOK_ROOT)/handbook.aux | cut -d= -f2) $(HANDBOOK_ROOT)/handbook-2up.pdf

print-answers: content $(PRINT_ANSWERS_TEX)

$(PRINT_ANSWERS_TEX): $(DERIVED_XML)
	@announce "Building $@ from $<"
	@echo "\\markfirstpage{$(SECTION)/$(BASE_NAME)/$(@:.tex=.pdf)}" > $@
	$(call xslt,$<,xml2latex.xsl,$(call xslt_parameter,department,'$(SUBJECT_CODE)'),$(call xslt_parameter,paper,'$(PAPER_NUMBER)'),$(call xslt_parameter,standalone,'no'),$(call xslt_parameter,showanswers,'yes'),$(call xslt_parameter,base-path,'sections/$(SECTION)/$(BASE_NAME)'),$(call xslt_parameter,image-format,'pdf')) >> $@
	@echo "\\marklastpage{$(SECTION)/$(BASE_NAME)/$(@:.tex=.pdf)}" >> $@

%-answers.pdf: %-answers.tex
	@announce "Generating $@"
	@$(if $(wildcard $(HANDBOOK_ROOT)/handbook-answers.pdf),,$(error $(HANDBOOK_ROOT)/handbook-answers.pdf is missing))
	@gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=$@ \
		-dFirstPage=$(shell grep '$@.1up.first' $(HANDBOOK_ROOT)/handbook-answers.aux | cut -d= -f2) \
		-dLastPage=$(shell grep '$@.1up.last' $(HANDBOOK_ROOT)/handbook-answers.aux | cut -d= -f2) $(HANDBOOK_ROOT)/handbook-answers.pdf

%-answers-2up.pdf: %-answers.tex
	@announce "Generating $@"
	@$(if $(wildcard $(HANDBOOK_ROOT)/handbook-answers-2up.pdf),,$(error $(HANDBOOK_ROOT)/handbook-answers-2up.pdf is missing))
	@gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=$@ \
		-dFirstPage=$(shell grep '$(@:-2up.pdf=.pdf).2up.first' $(HANDBOOK_ROOT)/handbook-answers.aux | cut -d= -f2) \
		-dLastPage=$(shell grep '$(@:-2up.pdf=.pdf).2up.last' $(HANDBOOK_ROOT)/handbook-answers.aux | cut -d= -f2) $(HANDBOOK_ROOT)/handbook-answers-2up.pdf


################################################################################
#
# Generate the derived XML source from the original XML template. The Big
# Ugly Regexp matches any file include directive (<@INC[ ]@>) and the
# script then simply inserts the content of that file into the output
# stream. This saves us having to fool around with XML document includes,
# which basically just don't work for us because they expect the included
# document to be a valid XML document. We really just want to include XML
# fragments. XInclude 1.0 supports this, but it's only just been finalised
# at the time of writing (January 2005).
#
$(DERIVED_XML): $(SOURCE_XML) $(XSLT_STYLESHEETS)
	@announce "Generating $@"
	@echo "<!-- THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! -->" > $@
	@echo >> $@
	@perl $(GLOBAL_HANDBOOK_INCLUDE)/include_content_files.pl $(PAPER_ROOT) $< >> $@
		

################################################################################
#
# If any of the content files change, touch the XML template to
# ensure that the document gets fully rebuilt.
#
$(SOURCE_XML): $(CONTENT_SRC)
	@touch $@


################################################################################
#
# Build the individual content files, etc., as necessary. Note above that
# all the primary targets depend on the "content" target, _before_
# anything else. This ensures that the content gets rebuilt (if necessary)
# before anything else happens.
# 
# This may seem redundant, given that SOURCE_XML depends on CONTENT_SRC in
# the rule above. However, this rule only works if we change one of the
# actual content files. What if instead, we change a graphic associated
# with one of the content files, without changing the content file itself?
# In this case, make assumes that nothing needs to be done, because the
# content file is older than the target, and this makefile knows nothing
# about any dependencies that the content file might have (that's dealt
# with by the makefile in the appropriate content directory). This is the
# main downside of having independent makefiles.
# 
# So, the slightly convoluted workaround using the empty "content" target
# ensures that the content files will always be checked, regardless of
# whether they've changed. If any of their prerequisites for a particular
# content file change, then the content file itself will be touched, thus
# activating the SOURCE_XML rule above. This process is particularly
# important to ensure that associated graphics get rebuilt when they
# change.
#
# All that aside, however, the _real_ trick is to ensure that this rule is
# only executed once. If we do a "make", this rule would nominally get
# executed four times (once for each of the targets that depend on
# "content"). The first time through the rule, therefore, we create a
# marker file called "content-checked". If this file exists, the rule
# isn't executed. These marker files are cleaned up by a one-off find
# command when the makefile is first run (see the include file
# one-time-init.make).
#
# Note: using the wildcard function to test for the existence of the
# marker file doesn't work when the makefile is run standalone, because
# the function result is expanded at the time the makefile is _loaded_,
# _before_ the one-time initialisation has a change to kick in and delete
# the file. This has the rather bizarre side-effect of the makefile only
# re-checking the content on every second run, and means that we can't use
# make conditionals to control rule execution. The solution is to prefix
# every command in the rule with "test ! -f" to check whether the marker
# file exists.
#
# Ack! The tribulations of not using a monolithic makefile...
#
content:
# Of course, there's little point in doing anything if there's no content!
ifneq ($(strip $(CONTENT_SRC)),)
	@if test ! -f content-checked; then \
		announce "Checking content for $(BASE_NAME)"; \
	fi
# Sanity check: do the content files referenced from the original source
# actually exist? The "cat</dev/null" is included because there needs to
# be something in the body of the if --- you can't just leave it empty. We
# can use the wildcard function here because these files should already
# exist --- if they don't, then by definition the filename is wrong!
	@if test ! -f content-checked; then \
		$(foreach f,$(CONTENT_SRC),$(if $(wildcard $f),,$(error Content file $f referenced from $(SOURCE_XML) does not exist)) cat</dev/null;) \
	fi
# Remake each individual content file.
	@if test ! -f content-checked; then \
		$(foreach f,$(CONTENT_SRC),if $(MAKE) -C $(dir $f) $(notdir $f) BUILD_DIR=$(CURDIR); then /usr/bin/true; else exit 1; fi;) \
	fi
# Create the marker file so that this rule doesn't get executed again.
	@if test ! -f content-checked; then \
		touch content-checked; \
	fi
endif


################################################################################
#
# Install the appropriate files on the web server. This relies on the
# environment variable HANDBOOK_INSTALL_DIRECTORY being defined, and
# (assuming that this variable points to a directory on the network) the
# appropriate share has been mounted.
#
# This checks each file in the build directory against the corresponding
# file in the install directory. The local file is only copied to the
# install directory if it is newer, or the remote file doesn't exist. I
# was going to do the "newer" testing using a target rule that dealt with
# each file individually, but for some weird reason it would ignore any
# additional files appended to the *_INSTALL_FILES variables. So I've done
# it with a foreach instead, which _does_ work. (Ick, although ironically
# the code is shorter.)
#
# !!! How do we get it to also install any extra files that we require
# for a particular content file (e.g., the SQL code for Tutorial 3)? !!!
#
install-questions:
	@test -d $(HANDBOOK_INSTALL_ROOT)
	@mkdir -p $(INSTALL_DIRECTORY)
	@$(foreach f,$(QUESTION_INSTALL_FILES),if test ! -f $(INSTALL_DIRECTORY)/$(f) -o $(f) -nt $(INSTALL_DIRECTORY)/$(f); then echo "Installing $(f)"; cp $(f) $(INSTALL_DIRECTORY); fi;)

install-answers:
	@test -d $(HANDBOOK_INSTALL_ROOT)
	@mkdir -p $(INSTALL_DIRECTORY)
	@$(foreach f,$(ANSWER_INSTALL_FILES),if test ! -f $(INSTALL_DIRECTORY)/$(f) -o $(f) -nt $(INSTALL_DIRECTORY)/$(f); then echo "Installing $(f)"; cp $(f) $(INSTALL_DIRECTORY); fi;)


################################################################################
#
# Clean up.
#
clean: web-clean print-clean
ifneq ($(strip $(ALL_CLEAN_FILES)),)
	-rm -f $(ALL_CLEAN_FILES)
endif


web-clean:
ifneq ($(strip $(WEB_CLEAN_FILES)),)
	-rm -f $(WEB_CLEAN_FILES)
endif


print-clean:
ifneq ($(strip $(PRINT_CLEAN_FILES)),)
	-rm -f $(PRINT_CLEAN_FILES)
endif


################################################################################
#
# Debugging information, mostly lists of the generated variables.
#
debug:
	@announce Externally defined variables
	@echo "TEACHING_SHARED = [$(TEACHING_SHARED)]"
	@echo "ALL_PAPERS_ROOT = [$(ALL_PAPERS_ROOT)]"
	@echo "HANDBOOK_INSTALL_ROOT = [$(HANDBOOK_INSTALL_ROOT)]"
	@echo "NOINIT = [$(NOINIT)]"
	@announce Internally defined variables
	@echo "GLOBAL_HANDBOOK_INCLUDE = [$(GLOBAL_HANDBOOK_INCLUDE)]"
	@echo "LOCAL_HANDBOOK_INCLUDE = [$(LOCAL_HANDBOOK_INCLUDE)]"
	@echo "SUBJECT_CODE = [$(SUBJECT_CODE)]"
	@echo "PAPER_NUMBER = [$(PAPER_NUMBER)]"
	@echo "PAPER_ROOT = [$(PAPER_ROOT)]"
	@echo "HANDBOOK_ROOT = [$(HANDBOOK_ROOT)]"
	@echo "SECTION = [$(SECTION)]"
	@echo "BASE_NAME = [$(BASE_NAME)]"
	@echo "SOURCE_XML = [$(SOURCE_XML)]"
	@echo "DERIVED_XML = [$(DERIVED_XML)]"
	@echo "WEB_QUESTIONS_HTML = [$(WEB_QUESTIONS_HTML)]"
	@echo "WEB_ANSWERS_HTML = [$(WEB_ANSWERS_HTML)]"
	@echo "PRINT_QUESTIONS_TEX = [$(PRINT_QUESTIONS_TEX)]"
	@echo "PRINT_ANSWERS_TEX = [$(PRINT_ANSWERS_TEX)]"
	@echo "PRINT_QUESTIONS_PDF_1UP = [$(PRINT_QUESTIONS_PDF_1UP)]"
	@echo "PRINT_ANSWERS_PDF_1UP = [$(PRINT_ANSWERS_PDF_1UP)]"
	@echo "PRINT_QUESTIONS_PDF_2UP = [$(PRINT_QUESTIONS_PDF_2UP)]"
	@echo "PRINT_ANSWERS_PDF_2UP = [$(PRINT_ANSWERS_PDF_2UP)]"
	@echo "DERIVED_WEB_FILES = [$(DERIVED_WEB_FILES)]"
	@echo "DERIVED_PRINT_FILES = [$(DERIVED_PRINT_FILES)]"
	@echo "XSLT_STYLESHEETS = [$(XSLT_STYLESHEETS)]"
	@echo "CONTENT_SRC = [$(CONTENT_SRC)]"
	@echo "INSTALL_DIRECTORY = [$(INSTALL_DIRECTORY)]"
	@echo "QUESTION_INSTALL_FILES = [$(QUESTION_INSTALL_FILES)]"
	@echo "ANSWER_INSTALL_FILES = [$(ANSWER_INSTALL_FILES)]"
	@echo "WEB_CLEAN_FILES = [$(WEB_CLEAN_FILES)]"
	@echo "PRINT_CLEAN_FILES = [$(PRINT_CLEAN_FILES)]"
	@echo "ALL_CLEAN_FILES = [$(ALL_CLEAN_FILES)]"


################################################################################
#
# Print out the list of targets. Handy for when you forget!
#
targets:
	@echo $(TARGETS)