################################################################################ # # 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/xml2xhtml.xsl $(TEACHING_SHARED)/Authoring/XML/xml2latex.xsl $(TEACHING_SHARED)/Authoring/XML/xml2xelatex.xsl ################################################################################ # # Extract the list of included filenames from the source template. Hooray # for the shell function and Perl! Include file paths are found within the # href attributes of XML Include elements (<xi:include href="..." />). # 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)/$$2\n" if m{<xi:include .*href="(\.\./)*([^"]+)"};' $(SOURCE_XML)) ################################################################################ # # Directory to install files into on web server. # INSTALL_DIRECTORY:=$(HANDBOOK_INSTALL_ROOT)/$(SUBJECT_CODE)$(PAPER_NUMBER)/$(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) $(wildcard *.png) ################################################################################ # # 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,subject-code,'$(SUBJECT_CODE)') $(call xslt_parameter,paper-number,'$(PAPER_NUMBER)') $(call xslt_parameter,paper-year,'$(PAPER_YEAR)') $(call xslt_parameter,period-code,'$(PAPER_PERIOD)') $(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,subject-code,'$(SUBJECT_CODE)') $(call xslt_parameter,paper-number,'$(PAPER_NUMBER)') $(call xslt_parameter,paper-year,'$(PAPER_YEAR)') $(call xslt_parameter,period-code,'$(PAPER_PERIOD)') $(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,subject-code,'$(SUBJECT_CODE)') $(call xslt_parameter,paper-number,'$(PAPER_NUMBER)') $(call xslt_parameter,paper-year,'$(PAPER_YEAR)') $(call xslt_parameter,period-code,'$(PAPER_PERIOD)') $(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') $(call xslt_parameter,paper-include-path,'$(PAPER_INCLUDE_PATH)') $(call xslt_parameter,latex-initialisation-file,'$(LATEX_INIT_FILE)')) >> $@ @$(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,subject-code,'$(SUBJECT_CODE)') $(call xslt_parameter,paper-number,'$(PAPER_NUMBER)') $(call xslt_parameter,paper-year,'$(PAPER_YEAR)') $(call xslt_parameter,period-code,'$(PAPER_PERIOD)') $(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') $(call xslt_parameter,paper-include-path,'$(PAPER_INCLUDE_PATH)') $(call xslt_parameter,latex-initialisation-file,'$(LATEX_INIT_FILE)')) >> $@ @$(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. # This is done by simply running the original source through xmllint # with the --xinclude option to process all the xi:include elements. # The result of this could have just been piped into the XSLT processor, # except that (a) not all of the processors support input from stdin, # and (b) combining both xmllint and XSLT processing into one command # means that make won't stop if there's any errors from xmllint. # # Sed is used to add a comment to the derived XML file, warning that this # is generated and shouldn't be edited. Sed was used because the <?xml?> # processing instruction MUST be on the first line. The implication here # is that all input source files have an <?xml?> processing instruction # (which they should anyway). # $(DERIVED_XML): $(SOURCE_XML) $(XSLT_STYLESHEETS) @$(ANNOUNCE) "Generating $@" @$(XMLLINT) --xinclude $< >$@ @$(SED) -i -e '1a <!-- THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! -->' $@ ################################################################################ # # 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 "$(TRUE)" 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)) $(TRUE);) \ 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 $(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 ################################################################################ # # Deploy the appropriate files into a shared folder, which is then synchronised # with Blackboard. This relies on the environment variable HANDBOOK_INSTALL_ROOT # 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 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: 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) "Deploying $(f)"; $(CP) $(f) $(INSTALL_DIRECTORY); fi;) install-answers: 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) "Deploying $(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) "ALL_PAPERS_ROOT = [$(ALL_PAPERS_ROOT)]" @$(ECHO) "TEACHING_SHARED = [$(TEACHING_SHARED)]" @$(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) "PAPER_INCLUDE_PATH = [$(PAPER_INCLUDE_PATH)]" @$(ECHO) "LATEX_INIT_FILE = [$(LATEX_INIT_FILE)]" @$(ECHO) "SUBJECT_CODE = [$(SUBJECT_CODE)]" @$(ECHO) "PAPER_NUMBER = [$(PAPER_NUMBER)]" @$(ECHO) "PAPER_YEAR = [$(PAPER_YEAR)]" @$(ECHO) "PAPER_PERIOD = [$(PAPER_PERIOD)]" @$(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)