-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.nw
5601 lines (4868 loc) · 193 KB
/
build.nw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% -*- mode: Noweb; noweb-code-mode: c-mode; -*-
% Build with noweb:
% notangle -t8 build.nw > makefile
% make
\documentclass[twoside,english]{article}
\usepackage[letterpaper,rmargin=1.5in,bmargin=1in]{geometry}
%%% latex preamble
% Begin-doc build-rcs
\RCS $Id$
\RCS $Revision$
\RCS $Date$
% End-doc build-rcs
\begin{document}
\title{Syntax Highlighting Modular Build System for noweb}
\author{Thomas J. Moore}
% Begin-doc build-ver
\date{Version 3.17.\RCSRevision\\\RCSDate}
% End-doc build-ver
\maketitle
\begin{abstract}
This document describes, implements, and is built using a modular ``literate
programming%
\footnote{\url{http://www.literateprogramming.com/articles.html}
is a good starting point.}%
'' build system with syntax highlighting, based on Norman Ramsey's noweb%
\footnote{\url{http://www.eecs.harvard.edu/~nr/noweb/}}%
, GNU%
\footnote{\url{http://www.gnu.org}}%
make, and a number of other freely available support tools.
This document, its contents and all prior versions published by the
same author are granted to the Public Domain in 2021 by Thomas J.
Moore.
\end{abstract}
\tableofcontents
\section{Overview}
% Begin-doc literate-intro
Most of my old personal programming projects started out as a general
idea, along with numerous scattered pieces of paper with notes
detaling some particular aspects of the design. Most of my
professional programming projects instead started with formal design
documents containing similar information. Over time, the design
documents and notes were lost and/or forgotten, and/or they diverged
from the actual product. In any case, the organization of any design
documentation rarely corresponds with the actual code, as efficient
code follows much different rules than efficient human communication.
Literate programming provides two main features that alleviate these
problems: the design document is kept with the source code, and the
code can be written in the order of the design document, to be
reordered later for more efficient compilation.
Literate programming is a method of programming devised and named by
Donald Knuth in 1983, along with the tools to help implement that
method (Web). It mainly emphasizes writing a human-readable document
with embedded code rather than the other way around. Over the years,
as others have adopted and adapted the style, the same term has been
used to mean slightly different things, and the tools have emphasized
different features. To me, the important features are mixing of
documentation and code, and reordering of code chunks to match the
text, rather than the other way around. Systems which do not display
the source code and documentation at the same time (e.g. Doxygen,
perl's pod) and/or do not provide code reordering (e.g. dtx, lgrind,
code2html) do not support literate programming. Knuth would probably
be even pickier, and reject my use of large code chunks, short
variable names for temporary variables, long sequences of multiple
code chunks, and multiple source files and programs in a single
document, as well as my lack of a language-specific symbol cross
reference and index (in addition to the chunk index).
Knuth felt that programs should be pleasant to read, like documents
which happen to have executable code in them. He believed that his
method would allow creation of programs which are by their nature
easier to maintain and less error-prone. I disagree. Whether or not
a document is pleasant to read is always very dependent on the writer
(and, to a lesser extent, the reader). Easier maintenance and fewer
bugs are but a pipe dream: people will in fact be even more tempted to
ignore the source code in favor of the commentary, thus missing
obvious bugs. Code reordering, and especially small code chunks may
also make it difficult for some to follow the code to find out what
\emph{really} gets executed. Instead of these lofty goals, my own
reasons for doing this are mainly to keep my scattered design notes in
one place (the source) and to keep the design document in sync with
the implementation (since they are the same thing).
Creating programs as documents using literate programming principles
requires a system that supports easy-to-write documentation, and code
that is both structured like the documentation and actually part of
the documentation. For simple programs, a set of tools which support
only a single source language and a single program file can be used.
As programs become more complicated, though, the number of source
files may increase, and when designing entire products, the number of
source languages may increase as well. Current common public
documentation standards also require a system which produces at least
HTML and PDF output. After long searching, I have not found any
systems which meet these requirements. Instead, the closest is the
noweb package%
\footnote{In particular, version 2. Version 3 is available and
functional, but has been in ``alpha'' for many years, has a different
command syntax, and is missing a few important bits (like the style
files for weaving and a [[noroots]]-like command, although those can
just be obtained from a version 2 archive). I'm also not particularly
fond of using a custom version of Lua (I converted it to use a
standard Lua 5.1 a while back, but lost all of that work somehow).
}. It supports \LaTeX{} and HTML output, and an arbitrary number of
output files and source languages. However, it does little to make the
code look like part of the documentation.
% End-doc literate-intro
The code in this document serves to provide a build system that
includes syntax-highlighted code, at the very least.
While the build system could simply be a carefully crafted shell script, a
makefile is used instead. This allows use of implicit rules and the
system-wide [[CC]] and [[CFLAGS]] defaults. The default chunk is set to the
makefile, requiring no chunk name to extract.
% Begin-doc build-doc
To build, place the noweb source files into their own directory (optionally
extracting them from the document you are reading first), extract the
makefile, and make using GNU make:
% Uses \jobname instead of \emph{<this file>} so that cut & paste works mostly
\begin{quote}{\ttfamily
\begin{rawhtml}
<!-->
\end{rawhtml}
\ifpdf
\#{} if your PDF viewer supports attachments, save the attachment\\*
\#{} otherwise, use pdfdetach:\\*
pdfdetach -saveall \jobname.pdf\\*
\#{} or pdftk:\\*
pdftk \jobname.pdf unpack\_files output .\\*
\else
\begin{rawhtml}
<-->
\end{rawhtml}
uudecode \jobname.html\\*
\#{} if uudecode chokes, use this instead:\\*
\#{} tr '\textbar$\sim$m`' '<>- ' < \jobname.html | uudecode -o \jobname.tar.gz\\*
gzip -dc \jobname.tar.gz | tar xf -\\*
\fi
\#{} then extract the makefile and build\\*
notangle -t8 build.nw > makefile\\*
make install
}\end{quote}
Note that while some dependency-based package managers may pull in a
full \TeX{} distribution as a dependency for noweb, none of that is
actually needed when only compiling code (i.e., tangling).
% End-doc build-doc
As noted below, additional makefile components are created in separate files.
% Begin-doc build-doc
Additional build configuration can be done in \texttt{makefile.config}
before installing (in particular, the install locations). This file can be
generated using either \texttt{make makefile.config} or \texttt{make -n}.
On the other hand, to avoid having to modify this file after cleaning,
\texttt{makefile.config.local} can be created for this purpose instead.
% End-doc build-doc
\subsection{Makefile}
The makefile is for building the support files, executable binaries, and
printable source code (HTML and PDF), maybe installing them, and cleaning up
afterwards. Configuration information, variable definitions, and
miscellaneous rules are extracted from not only this noweb file, but also
any others that use it. The makefile itself can be used to include that
information, but to simplify makefile extraction, all of that information is
kept in separate files. Modules that wish to extend the makefile can do so
using [[<<makefile.config>>]] for user-adjustable variables,
[[<<makefile.vars>>]] for any other variable definitions, and
[[<<makefile.rules>>]] for new rules. Some configuration variables may need
values before the configuration file is built, though, so defaults are
included in the makefile, to be overridden by the configuration file. Note
that any variable which does not depend on unknowns (noweb chunks or
variables not yet defined) should be defined using GNU make's ``simply
expanded'' variable flavor (i.e., [[:=]] instead of plain [[=]]) for
improved performance. This has a disadvantage in that overriding simply
defined variables will require overriding any of its dependents as well, but
the performance improvement is usually worth it.
I have made some adjustments to the ordering of this in order to
support Daniel Pfeiffer's Makepp%
\footnote{\url{http://makepp.sourceforge.net}}%
, a GNU make clone which adds MD5 checksums as a file change detection
mechanism. This magically fixes the problem of rebuilding every time,
at the expense of dealing with Makepp-specific issues. The first of
these is that include files cannot be automatically (re)built unless
they appear after the rules which create them. This means that the
two variables which control their creation can no longer come from the
configuration file. They can only be set using the command line.
Another, related issue is that even though Makepp solves the rebuild
problem for most files, it makes the problem worse for the makefile
and its include files. These are unconditionally rebuilt every single
time makepp is invoked. The use of Makepp is detected by checking for
the existence of a non-empty [[$(MAKEPP_VERSION)]].
\lstset{language=make}
<<*>>=
<<makefile>>
@
<<makefile>>=
# See makefile.config for variables to override
# Put local config in makefile.config or makefile.config.local
<<Common noweb Warning>>
# default rule
all: misc bin doc
ifneq ($(MAKEPP_VERSION),)
# not much point, since it doesn't filter out #line directives
# and therefore most changes to noweb source will result in file changes
signature c_compilation_md5
endif
<<Defaults for [[makefile.config]]>>
# This is the right place to include them, but makepp can't handle it
#-include makefile.config
-include makefile.config.local
<<Build variables for makefile includes>>
<<Build rules for makefile includes>>
-include makefile.config
# reinclude to ensure overrides
-include makefile.config.local
-include makefile.vars
# keep intermediate files
.SECONDARY:
# mark a few phonies - not really necessary
.PHONY: all misc bin doc install clean distclean
bin: $(LIB_FILES) $(EXEC_FILES)
doc: $(DOC_FILES)
misc: $(MISC_FILES)
-include makefile.rules
@
<<Common noweb Warning>>=
# GENERATED FILE: DO NOT EDIT OR READ THIS FILE
# Instead, read or edit the noweb file(s) from which this was generated,
# listed below. Copyright notice and license terms can be found there.
# $Id$
@
\lstset{language=txt}
<<Sources>>=
$Id$
@
\lstset{language=make}
<<makefile.rules>>=
<<Common noweb Warning>>
install: misc bin
@for x in $(EXEC_FILES); do set -e; \
echo "install $(DESTDIR)$(BIN_DIR)/$$x"; \
mkdir -p $(DESTDIR)$(BIN_DIR); \
rm -f $(DESTDIR)$(BIN_DIR)/$$x; \
cp -p $$x $(DESTDIR)$(BIN_DIR); \
done
<<Install other files>>
clean:
<<Clean temporary files>>
rm -f $(filter-out makefile, $(MAKEFILES))
distclean: clean
<<Clean built files>>
rm -rf .makepp
<<Remove makefile>>
@
<<makefile.config>>=
# Configuration parameters
# Change here only if:
# - you do not use makepp (makepp will overwrite on every make)
# - you do not modify the noweb sources (otherwise even GNU make overwrites)
# Better to copy to makefile.config.local and make changes there.
# You can, of course, also specify overrides on the command line
<<Installation Directory Configuration Options>>
@
<<Installation Directory Configuration Options>>=
# Installation prefix to apply to all install locations
# This is not applied to hard-coded paths within installed files
DESTDIR:=
# Installation prefix to apply to all install locations by default
# This is applied after DESTDIR.
prefix:=/usr/local
# Installation directory for binaries
BIN_DIR:=$(prefix)/bin
@
<<makefile.vars>>=
<<Common noweb Warning>>
MAKEFILES = makefile makefile.config makefile.vars makefile.rules
EXEC_FILES = <<Executables>>
LIB_FILES = <<Libraries>>
DOC_FILES = <<Source Code Documentation Files>>
MISC_FILES = <<Plain Files>>
@
<<Install other files>>=
@
<<Plain Files>>=
\
@
<<Clean built files>>=
rm -rf $(DOC_FILES)
@
The makefile can make itself, as well. This is dependent only on its
source file; there is little point in making this dependent on the
included files, as they will be automatically rebuilt as needed,
anyway. A quick check before writing out the file ensures that a
blank or otherwise seriously invalid makefile will never be created
due to errors in the source file. However, [[-include]] statements
are filtered out, so that GNU make doesn't try to automatically build
the include files. This was never a perfect check, and this filtering
makes it less perfect: each file must be independently error-free, and
errors introduced from other files (such as variable definitions)
which may affect the file being tested will not be detected.
Note that Makepp has issues with recursive make invocation for the
verification of the makefile, so this step is skipped. More recent
versions claim to have fixed this, but I have yet to test it.
<<makefile.config>>=
# The name of the file containing the makefile
# Note: due to Makepp restrictions, this can only be set on the command line
# or in makefile.config.local
#BUILD_NOWEB:=build.nw
@
<<Defaults for [[makefile.config]]>>=
BUILD_NOWEB=build.nw
@
<<Build rules for makefile includes>>=
makefile: $(BUILD_NOWEB)
notangle -t8 -R$@ $(BUILD_NOWEB) 2>/dev/null | grep -v '^$$' >/dev/null
ifeq ($(MAKEPP_VERSION),)
notangle -t8 -R$@ $(BUILD_NOWEB) 2>/dev/null | \
grep -v '^-include' | \
env -i PATH="$${PATH}" $(MAKE) -n -f- /dev/null >/dev/null
endif
-notangle -t8 -R$@ $(BUILD_NOWEB) > $@
@
<<Remove makefile>>=
rm -f makefile
@@echo
@@echo Regenerate the makefile with:
@@echo notangle -t8 $(BUILD_NOWEB) \> makefile
@
Generating the other files requires the ability to correctly assemble them
from all the other noweb files.
<<makefile.config>>=
# The name of the source files
# Any time you change this, makefile.* should be removed and rebuilt
# Note: due to Makepp restrictions, this can only be set on the command line
# or in makefile.config.local
#NOWEB:=$(wildcard *.nw)
@
<<Defaults for [[makefile.config]]>>=
NOWEB:=$(wildcard *.nw)
@
\subsection{Merging Sources}
From these files, an order of building must be derived. This is done using
a dependency tree created from special comments in the source files. These
comments are at the beginning of a line, and are of the form
\texttt{\%\%\% requires \emph{X}}, where \emph{X} is either the name of a
noweb file or the name with the \texttt{.nw} removed. An explicit
\verb|\input{|\emph{X}\verb|.nw| creates a dependency as well.
Top-level build files are those on which no others depend. The tree
order consists of the top-level files, followed by their direct
dependencies, in the order found in those files, followed by those
files' direct dependencies, and so forth, with no file repeated.
Finding the dependency directives requires [[egrep]], and removing the
pattern requires [[sed]]. Due to the sloppiness of the patterns and other
parsing, no file names can contain colons or spaces. This is all done using
GNU make's internal functions so that the results can be easily used in
other rules.
<<Build variables for makefile includes>>=
NOWEB_DEPS:=$(shell egrep '^%%% requires |^\\input{.*\.nw}$$' $(NOWEB) /dev/null | \
sed -e 's/%%% requires //;s/\\input{\(.*\)}$$/\1/' \
-e 's/[[:space:]]*\(%.*\)*$$//;s/:/ /')
@
After the dependencies are found, they are separated into files which depend
on others ([[NOWEB_UPPER]]), and files which are depended on
([[NOWEB_LOWER]]). The files whilch are depended on may be specified
without the \texttt{.nw} extension, so the filesystem is checked for the
file, and \texttt{.nw} is added if the file does not exist. If it still
does not exist after tacking on \texttt{.nw}, the missing file is an error.
<<Build variables for makefile includes>>=
# return rest of words of parm 2, starting at parm 1
rest = $(wordlist $1,$(words $2),$2)
# return every other word of parm, starting with first
ret_other = $(word 1,$1) $(if $(word 3,$1),$(call ret_other,$(call rest,3,$1)))
NOWEB_UPPER:=$(call ret_other,$(NOWEB_DEPS))
NOWEB_LOWER_BASE:=$(call ret_other,$(wordlist 2,$(words $(NOWEB_DEPS)),$(NOWEB_DEPS)))
NOWEB_LOWER:=$(foreach f,$(NOWEB_LOWER_BASE),$(if $(wildcard $f),$f,\
$(if $(wildcard $f.nw),$f.nw, \
$(error $f and $f.nw not found))))
@
Next, the tree is traversed, from top to bottom. The top is simply the list
of files on which no other files depend. There must be at least one file at
the top, or nothing will work correctly. Then, each file in the list is
checked for as-yet unfulfilled dependencies to tack on. No dependency may
appear before files upon which it depends, so the dependecies are repeatedly
tacked onto the start of the list and stripped from the rest until the tree
settles.
<<Build variables for makefile includes>>=
NOWEB_HIGHEST:=$(filter-out $(NOWEB_LOWER),$(NOWEB))
$(if $(NOWEB_HIGHEST),,$(error Invalid dependency tree))
# return words from parm #3 in positions of parm #2 which match parm #1
match_words = $(if $(filter $1,$(word 1,$2)),$(word 1,$3)) \
$(if $(word 2,$2),$(call match_words,$1,$(call rest,2,$2),$(call rest,2,$3)))
# return only unique words in parm, keeping only first occurrence
uniq = $(if $1,\
$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$(call rest,2,$1))))
# tack dependencies to left
predeps = $(call uniq, $(foreach f,$1,\
$(call match_words,$f,$(NOWEB_UPPER),$(NOWEB_LOWER))) $1)
# true if lists not equal
listne = $(strip $(if $1,$(if $2,$(if $(filter $(word 1,$1),$(word 1,$2)), \
$(call listne,$(call rest,2,$1),$(call rest,2,$2)), \
y1), y2), $(if $2, y3)))
# expand dependencies until there are no more
tree = $(if $(call listne,$1,$(call predeps,$1)), \
$(call tree,$(call predeps,$1)), $1)
NOWEB_ORDER:=$(call tree,$(NOWEB_HIGHEST))
ifeq ($(PROJECT_NAME),)
PROJECT_NAME:=$(subst .nw,,$(firstword $(NOWEB_HIGHEST)))
endif
@
<<makefile.config>>=
#Set to override the automatically determined project name
#PROJECT_NAME=
@
<<makefile.rules>>=
prtree:
@echo Project: $(PROJECT_NAME)
@echo Deps: $(NOWEB_DEPS)
@echo Highest: $(NOWEB_HIGHEST)
@echo Upper: $(NOWEB_UPPER)
@echo Lower: $(NOWEB_LOWER)
@echo Order: $(NOWEB_ORDER)
@
So, to generate a file, all of these noweb files are concatenated, in
reverse order, and passed into [[notangle]]. The makefile components in
particular need to be checked for errors in mostly the same way as the main
makefile.
<<Build rules for makefile includes>>=
makefile.config: $(NOWEB_ORDER)
notangle -t8 -R$@ $^ 2>/dev/null | grep -v '^$$' >/dev/null
ifeq ($(MAKEPP_VERSION),)
notangle -t8 -R$@ $^ 2>/dev/null | \
grep -v '^-include' | \
env -i PATH="$${PATH}" $(MAKE) -n -f- /dev/null >/dev/null
endif
-notangle -t8 -R$@ $^ > $@
makefile.vars: makefile.config
notangle -t8 -R$@ $(NOWEB_ORDER) 2>/dev/null | grep -v '^$$' >/dev/null
ifeq ($(MAKEPP_VERSION),)
notangle -t8 -Rmakefile.config -R$@ $(NOWEB_ORDER) 2>/dev/null | \
grep -v '^-include' | \
env -i PATH="$${PATH}" $(MAKE) -n -f- /dev/null >/dev/null
endif
-notangle -t8 -R$@ $(NOWEB_ORDER) > $@
makefile.rules: makefile.vars
notangle -t8 -R$@ $(NOWEB_ORDER) 2>/dev/null | grep -v '^$$' >/dev/null
ifeq ($(MAKEPP_VERSION),)
notangle -t8 -Rmakefile.config -Rmakefile.vars -R$@ \
$(NOWEB_ORDER) 2>/dev/null | \
grep -v '^-include' | \
env -i PATH="$${PATH}" $(MAKE) -n -f- /dev/null >/dev/null
endif
-notangle -t8 -R$@ $(NOWEB_ORDER) > $@
@
Building plain files is done the same way, but without the complicated
checks, assuming that no additional processing needs to be done. For files
where additional processing is necessary, additional dependencies on the
[[misc]] target, as well as [[<<Install other files>>]] can be used to add
files with special build rules. Another way to add additional
processing steps is to modify the tangling pipeline (by adding
[[NOTANGLE_OPTS]] or replacing [[NOTANGLE]]) or to add a
post-processing pipeline (using [[NOTANGLE_POSTPROC]]). GNU make
allows setting these variables per-target, making adding processing to
just one or two files relatively easy. In order to allow for tangle
postprocessing to use locally built items, [[NOTANGLE_DEP]] can be
altered as well. To make using this pipeline less error-prone (and
shorter), a single-argument function is provided to extract a given
named chunk ([[NOTANGLE_CHUNK]]). Almost every usage I envision
uses [[$@]] as that argument, and redirects output to [[$@]]. No
special variable is set to that usage pattern, though.
Note that [[<<Plain Files>>]] is intended for installable targets.
For plain files generated as part of the build, use
[[<<Plain Build Files>>]] instead. For a subtle change, any files
generated by means other than [[notangle]] can be added to
[[<<Plain Built Files>>]] in order to save making a clean rule for it.
<<makefile.vars>>=
NOTANGLE=notangle
NOTANGLE_DEP=
NOTANGLE_OPTS=
NOTANGLE_POSTPROC=
NOTANGLE_CHUNK=$(NOTANGLE) $(NOTANGLE_OPTS) -R$1 $(NOWEB_ORDER) $(NOTANGLE_POSTPROC)
MISC_TEMP_FILES = <<Plain Build Files>>
GENERATED_TEMP_FILES = <<Plain Built Files>>
@
<<Plain Build Files>>=
\
@
<<Plain Built Files>>=
\
@
<<makefile.rules>>=
$(MISC_FILES) $(MISC_TEMP_FILES): $(NOWEB_ORDER) $(NOTANGLE_DEP)
-$(call NOTANGLE_CHUNK,$@) >$@
@
<<Clean built files>>=
rm -f $(MISC_FILES)
@
<<Clean temporary files>>=
rm -f $(MISC_TEMP_FILES) $(GENERATED_TEMP_FILES)
@
\subsection{Support Files}
Since [[noroots]] may not be on the target system, for example when using
the tar file, a close equivalent is provided. This does not properly
filter out non-root nodes, but most build operations require specific
node names that are not likely to be used anywhere but the root level.
Makepp has trouble parsing this, so this is disabled for now.
<<Defaults for [[makefile.config]]>>=
NOROOTS=noroots
@
<<makefile.config>>=
# The noroots command is used to extract file names from $(NOWEB)
# The following works well enough in most cases if noweb is missing
# Note: due to Makepp restrictions, this can only be set on the command line
# or in makefile.config.local
#NOROOTS=sh -c "sed -n '/^@<<.*@>>=\$$/{s/=\$$//;p;}' \$$* \
# | sort -u" /dev/null
NOROOTS=noroots
@
In fact, when building just the code, it is likely that the only part
of noweb required is [[notangle]]. However, with the extensions of
the next section (in particular parameterized chunks), a simple
replacement (as provided in previous versions of this document) is not
possible.
\subsection{Additional Features}
It may also be useful to build a tar file for building on systems where
noweb is not present. This is meant to be a convenience, for building
binaries only, and not for distribution. That means neither the source
documentation nor any means to build the source documentation will be
included. Since it is not possible to distinguish between soft links
created for building and soft links to other noweb files, no attempt will be
made to force link dereferencing, either.
<<makefile.rules>>=
$(PROJECT_NAME)-src.tar.gz: $(BUILD_SOURCE)
@# needs GNU tar
tar czf $(PROJECT_NAME)-src.tar.gz $(BUILD_SOURCE)
@
<<makefile.vars>>=
BUILD_SOURCE=$(NOWEB) $(MAKEFILES) $(MISC_FILES) <<Build Source>>
@
<<Clean built files>>=
rm -f $(PROJECT_NAME)-src.tar.gz
@
It may also be useful to get some source code statistics. Code counts
include all code chunks, but do not include the chunk name/start line or the
terminating [[@]]. Hidden sections are not included; they are delimited by
\texttt{<!-\,->} and \texttt{<-\,->} on their own line.
<<makefile.rules>>=
count: $(NOWEB) $(MISC_FILES) <<Files to Count>>
@for x in $(NOWEB); do \
echo $${x}:; \
<<Count lines in [[$$x]]>> \
done
@echo "Tangled output:"
@wc -l $(MISC_FILES) <<Files to Count>>
</dev/null | sort -k1n
@
<<Count lines in [[$$x]]>>=
tl=`cat $$x | wc -l`; \
bl=`grep -c '^[[:space:]]*$$' $$x`; \
hl=`sed -n -e '/^<!-->$$/{:a p;n;/^<-->$$/!ba;}' < $$x | wc -l`; \
cl=`sed -n -e '/^@<<.*>>=/{:a n;/^@[^@]/b;/^@$$/b;/^[[:space:]]*$$/!p;ba;}' \
-e '/^<!-->$$/{:b n;/^<-->$$/!bb;}' < $$x | wc -l` ; \
echo " Lines: $$tl:"; \
echo " $$cl code, $$((tl-bl-hl-cl)) doc, $$hl hidden, $$bl blank";
@
Finally, there is a rule to check the consistency of the noweb source.
Checking the list of chunks which are not specifically referenced
requires human intervention, so all root chunks are printed out for
review. The list of missing chunks should always be accurate, though.
The terminating [[@]] for code chunks isn't required by the noweb
syntax when writing multiple consecutive code chunks, but it is
required by some of the document converters. In addition, if it is
not followed by a blank line, the weaver will likely produce invalid
\LaTeX{}. The (obsolescent, but still necessary) [[%def]] directive is
broken in that if the next non-blank text is another code chunk, it
corrupts the header, so that is flagged as well. The reinsertion code
is stupid about unbalanced doc reinsertions, but instead of checking
correctly there, it's flagged here.
Because this check is a separate rule, target-specific altering of the
tangling process is not supported. Making drastic changes to source
code using the tangle process alterations should not be done anyway,
and rules like this make some kinds of alterations impossible.
<<makefile.rules>>=
.PHONY: check
check: $(NOWEB) $(NOTANGLE_DEP)
@# Print all roots to find misnamed/unreferenced chunks
@# At same time, untangle each to find undefined chunks
@ <<[[noroots]]>> $(NOWEB) | sed 's/@<<//;s/@>>//;' | sort | while read -r x; do \
echo "Root: $$x"; \
$(call NOTANGLE_CHUNK,"$$x") >/dev/null; \
done
@# Check for proper @-usage and %def-usage
@for f in $(NOWEB); do \
lno=1 gotat=y gotdef=; \
while IFS= read -r x; do \
test -n "$${gotat#y}" -a -n "$$x" && echo "$$f: $${lno}: no blank line after @"; \
gotat=$${gotat%x}; \
case "$$x" in \
"@<<"*"@>>"=) \
test "$$gotdef" && echo "$$f: $${lno}: %def w/o following text"; \
test "$$gotat" || echo "$$f: $$prev not terminated by @"; \
gotat=; \
prev="$${lno}: $$x";; \
"@@<<"*|"@@>>"*|"@[["|"@]]") ;; \
@|@[^@]*) \
test "$$gotat" && echo "$$f: $${lno}: extra @"; \
gotat=yx; \
case "$$x" in \
"@ %def "*) gotdef=y ;; \
esac;; \
?*) gotdef= ;; \
esac; \
lno=$$((lno+1)); \
done < "$$f"; \
done
<<Other source file consistency checks>>
@
\section{Binaries}
Building the binaries is pretty simple, using automatic rules.
Support is provided here for plain text executable scripts and C and
C++ executables. In order to ensure a consistent build rule for all C
and C++ files, the default C/C++-to-executable rules are blanked out.
Executables are assumed to have one mainline source, named the same as
the executable but with a \texttt{.c} extension for C and
\texttt{.c++} for C++. All local libraries built from local sources
are linked into every executable.
All source files share the ability to have per-file post-processing
using [[NOTANGLE_POSTPROC]]. Previous versions of this build system
only supported per-file post-processing for C files, using the
[[C_POSTPROCESS]] hook. The C-specific hook was a function call
rather than a plain variable expansion; i.e., instead of using [[$@]]
to refer to the target, it was expected that [[$1]] would be used.
Using [[NOTANGLE_POSTPROC]] is more consistent, but some old documents
still use the old method. The old method is supported, but
deprecated.
<<Executables>>=
$(C_EXEC) $(SCRIPT_EXEC) \
@
<<makefile.vars>>=
SCRIPT_EXEC=<<Script Executables>>
C_EXEC=<<C Executables>>
NOWEB_CFILES:=$(shell $(NOROOTS) $(NOWEB) | sed -n '/\.c>>/{s/@<<//;s/@>>//;p;}')
CFILES=<<C Files>>
NOWEB_RAWCXXFILES:=$(shell $(NOROOTS) $(NOWEB) | sed -n '/\.c++>>/{s/@<<//;s/@>>//;p;}')
<<Adjust [[NOWEB_CXXFILES]]>>
CXXFILES=<<C++ Files>>
NOWEB_HEADERS:=$(shell $(NOROOTS) $(NOWEB) | sed -n '/\.h>>/{s/@<<//;s/@>>//;p;}')
CHEADERS=<<C Headers>>
CXXHEADERS=<<C++ Headers>>
COFILES=$(patsubst %.c, %.o, $(CFILES))
CXXOFILES=$(patsubst %.c++, %.o, $(CXXFILES))
@
<<C Files>>=
$(NOWEB_CFILES) \
@
<<C++ Files>>=
$(NOWEB_CXXFILES) \
@
<<C Headers>>=
$(filter-out $(CXXHEADERS),$(NOWEB_HEADERS)) \
@
<<C++ Headers>>=
\
@
<<Build Source>>=
$(SCRIPT_EXEC) \
@
<<makefile.config>>=
# Set to -L for #line in C/C++ files, but lose code indentation
USE_LINE:=-L
@
<<makefile.vars>>=
$(NOWEB_HEADERS) $(NOWEB_CFILES) $(NOWEB_CXXFILES): NOTANGLE_OPTS += $(USE_LINE)
$(NOWEB_HEADERS) $(NOWEB_CFILES): NOTANGLE_POSTPROC += $(call C_POSTPROCESS,$@)
@
<<makefile.rules>>=
$(SCRIPT_EXEC): $(NOWEB) $(NOTANGLE_DEP)
-$(call NOTANGLE_CHUNK,$@) >$@
chmod +x $@
$(NOWEB_HEADERS) $(NOWEB_CFILES) $(NOWEB_CXXFILES): $(NOWEB) $(NOTANGLE_DEP)
-$(call NOTANGLE_CHUNK,$@) >$@
# disable gmake built-in .c/.c++->exe rules
ifeq ($(MAKEPP_VERSION),)
%: %.c
%: %.c++
endif
%.o: %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<
%.o: %.c++
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) -c -o $@ $<
%: %.o
$(CXX) -o $@ $< $(LDFLAGS) -L. $(LOCAL_LIBS) $(EXTRA_LDFLAGS)
@
<<Files to Count>>=
$(CFILES) $(CXXFILES) $(CHEADERS) $(CXXHEADERS) \
@
<<Clean temporary files>>=
rm -f $(CFILES) $(CXXFILES) $(CHEADERS) $(CXXHEADERS)
rm -f $(COFILES) $(CXXOFILES)
@
<<Clean built files>>=
rm -f $(EXEC_FILES)
@
<<Script Executables>>=
\
@
<<C Executables>>=
\
@
<<Build Source>>=
$(CFILES) $(CXXFILES) $(CHEADERS) $(CXXHEADERS) \
@
Scripts and C/C++ programs may also be generated as part of the build
process, for example to create machine-generated code.
<<makefile.vars>>=
BUILD_SCRIPT_EXEC=<<Build Script Executables>>
BUILD_C_EXEC=<<C Build Executables>>
@
<<Build Source>>=
$(BUILD_SCRIPT_EXEC) \
@
<<makefile.rules>>=
$(BUILD_SCRIPT_EXEC): $(NOWEB) $(NOTANGLE_DEP)
-$(call NOTANGLE_CHUNK,$@) >$@
chmod +x $@
@
<<Clean temporary files>>=
rm -f $(BUILD_SCRIPT_EXEC) $(BUILD_C_EXEC)
@
<<Build Script Executables>>=
\
@
<<C Build Executables>>=
\
@
The local libraries are built using a chunk naming convention.
[[<<Library [[name]] Members>>]] chunks contain a plain listing of included
object files. No support for shared libraries is provided at this time.
Ideally, this should ensure that libraries dependent on others are listed
earlier in the library order. Instead, the list is printed in reverse
order, so the least dependent libraries are printed first.
Since \texttt{tac} may not be available everywhere, an alternative may
be specified in [[makefile.config]].
<<Defaults for [[makefile.config]]>>=
TACCMD=tac
@
<<makefile.config>>=
# Where to find the non-standard tac command (GNU coreutils)
# Note: due to Makepp restrictions, this can only be set on the command line
# or in makefile.config.local
#TACCMD:=sed -n -e '1!G;h;$$p'
@
<<Build variables for makefile includes>>=
LOCAL_LIBS_BASE:=$(shell $(NOROOTS) $(NOWEB_ORDER) | \
sed -n 's/@<<Library \[\[\(.*\)]] Members@>>/\1/p' | \
$(TACCMD))
LOCAL_LIBS:=$(LOCAL_LIBS_BASE:%=-l%)
LOCAL_LIB_FILES:=$(LOCAL_LIBS_BASE:%=lib%.a)
@
<<makefile.rules>>=
$(C_EXEC): $(LOCAL_LIB_FILES)
@
<<Clean temporary files>>=
rm -f $(LOCAL_LIB_FILES)
@
While it would be nice to not have to generate yet another support file for
this, notangle is required for this to work. One of the goals of this
system is to be able to generate a source tarball that does not depend on
notangle.
<<Defaults for [[makefile.config]]>>=
AR=ar
AR_EXTRA=
RANLIB=ranlib
@
<<makefile.config>>=
# The ar program
AR=ar
# Extra $(AR) options (e.g. s to perform ranlib automatically)
AR_EXTRA=
# The ranlib program
RANLIB=ranlib
@
<<makefile.rules>>=
#define build-lib
#lib$(1).a: $(2)
# rm -f $$@
# $$(AR) cr$$(AR_EXTRA) $$@ $$^
# $$(RANLIB) $$@
#LIB_OBJ += $(filter-out $(COFILES),$(2))
#endef
#
#$(foreach l,$(LOCAL_LIBS_BASE),$(eval $(call build-lib,$l, \
# $(shell notangle -R'Library [[$l]] Members' $(NOWEB_ORDER) 2>/dev/null))))
@
Instead, [[makefile.libs]] is generated with the macros expanded.
Since the chunks are simple, and this is for makefile building, there
is no reason to support anything but the basic [[notangle]].
<<makefile>>=
MAKEFILES+=makefile.libs
makefile.libs: makefile.rules
for x in $(LOCAL_LIBS_BASE); do \
printf %s lib$$x.a:; \
notangle -R"Library [[$$x]] Members" $(NOWEB_ORDER) | tr '\n' ' '; \
printf '\n\trm -f $$@\n'; \
printf '\t$$(AR) cr$$(AR_EXTRA) $$@ $$^\n'; \
printf '\t$$(RANLIB) $$@\n'; \
printf 'LIB_OBJ += $$(filter-out $$(COFILES) $$(CXXOFILES),'; \
notangle -R"Library [[$$x]] Members" $(NOWEB_ORDER) | tr '\n' ' '; \
printf ')\n'; \
done > $@
-include makefile.libs
@
<<Clean temporary files>>=
rm -f $(LIB_OBJ)
@
Of course it might be useful to also provide installation rules for
selected libraries, as well as their support files (header files and
API documentation), but that is left for a future revision. For now,
a simplistic method is provided to add headers to an exported header
list, and libraries to an exported library list. Configuration files
can be installed using an exported misc. file list. If the libraries
are converted into shared objects, they are not exported using this
method. As a special hack, a subdirectory may be prefixed to the
include file and misc file names, and they may be exported either from
the named subdirectory or from the current directory. Also, the
library names only need the base name; if this convention is followed,
it may be easier to install shared libraries in the future. In any
case, no additional dependencies should be required for the
[[install]] target, as everything here should have been built by the