Merged revisions 1884-1905 via svnmerge from
[scons.git] / doc / SConscript
1 #
2 # SConscript file for building SCons documentation.
3 #
4
5 #
6 # __COPYRIGHT__
7 #
8 # Permission is hereby granted, free of charge, to any person obtaining
9 # a copy of this software and associated documentation files (the
10 # "Software"), to deal in the Software without restriction, including
11 # without limitation the rights to use, copy, modify, merge, publish,
12 # distribute, sublicense, and/or sell copies of the Software, and to
13 # permit persons to whom the Software is furnished to do so, subject to
14 # the following conditions:
15 #
16 # The above copyright notice and this permission notice shall be included
17 # in all copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
20 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
21 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 #
27
28 import os.path
29 import re
30 import string
31
32 Import('build_dir', 'env', 'whereis')
33
34 env = env.Clone()
35
36 env.TargetSignatures('content')
37
38 build = os.path.join(build_dir, 'doc')
39
40 #
41 #
42 #
43 doc_tar_gz = os.path.join(build_dir,
44                           'dist',
45                           'scons-doc-%s.tar.gz' % env.Dictionary('VERSION'))
46
47 #
48 # We'll only try to build text files (for some documents)
49 # if lynx is available to do the dump.
50 #
51 fig2dev = whereis('fig2dev')
52 epydoc = whereis('epydoc')
53 groff = whereis('groff')
54 lynx = whereis('lynx')
55 man2html = whereis('man2html')
56 jade = whereis('jade')
57 jadetex = whereis('jadetex')
58 pdfjadetex = whereis('pdfjadetex')
59 jw = whereis('jw')
60 tidy = whereis('tidy')
61
62 tar_deps = []
63 tar_list = []
64
65 entity_re = re.compile(r'<!entity\s+(?:%\s+)?(?:\S+)\s+SYSTEM\s+"([^"]*)">', re.I)
66 format_re = re.compile(r'<(?:graphic|imagedata)\s+fileref="([^"]*)"(?:\s+format="([^"]*)")?')
67
68 #
69 # Find internal dependencies in .sgml files:
70 #
71 #   <!entity bground SYSTEM "bground.sgml">
72 #   <graphic fileref="file.jpg">
73 #   <imagedata fileref="file.jpg">
74 #
75 # This only finds one per line, and assumes that anything
76 # defined as a SYSTEM entity is, in fact, a file included
77 # somewhere in the document.
78 #
79 def scansgml(node, env, target):
80     includes = []
81
82     contents = node.get_contents()
83
84     includes.extend(entity_re.findall(contents))
85
86     matches = format_re.findall(contents)
87     for m in matches:
88         file, format = m
89         if format and file[-len(format):] != format:
90             file = file + '.' + format
91         if not os.path.isabs(file):
92             a = []
93             f = file
94             while f:
95                 f, tail = os.path.split(f)
96                 if tail == 'doc':
97                     break
98                 a = [tail] + a
99             file = apply(os.path.join, a, {})
100         includes.append(file)
101
102     return includes
103
104 s = Scanner(name = 'sgml', function = scansgml, skeys = ['.sgml', '.mod'])
105
106 orig_env = env
107 env = orig_env.Clone(SCANNERS = [s],
108                      SCONS_PROC_PY = File('#bin/scons-proc.py').rfile(),
109                      SCONSOUTPUT_PY = File('#bin/sconsoutput.py').rfile())
110
111 # Fetch the list of files in the build engine that contain
112 # SCons documentation XML for processing.
113 def chop(s): return s[:-1]
114
115 # If we ever read doc from __scons_doc__ strings in *.py files again,
116 # here's how it's done:
117 #manifest_in = File('#src/engine/MANIFEST.in').rstr()
118 #manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr()
119 #scons_doc_files = map(chop, open(manifest_in).readlines() +\
120 #                            open(manifest_xml_in).readlines())
121 #scons_doc_files = map(lambda x: '#src/engine/'+x, scons_doc_files)
122 #manifest_in = File('#src/engine/MANIFEST.in').rstr()
123
124 manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr()
125 scons_doc_files = map(chop, open(manifest_xml_in).readlines())
126 scons_doc_files = map(lambda x: File('#src/engine/'+x).rstr(), scons_doc_files)
127
128 if jw:
129     #
130     # Always create a version.sgml file containing the version information
131     # for this run.  Ignore it for dependency purposes so we don't
132     # rebuild all the docs every time just because the date changes.
133     #
134     date, ver, rev = env.Dictionary('DATE', 'VERSION', 'REVISION')
135     version_sgml = File(os.path.join(build, "version.sgml"))
136     #version_sgml = File("version.sgml")
137     verfile = str(version_sgml)
138     try:
139         os.unlink(verfile)
140     except OSError:
141         pass    # okay if the file didn't exist
142     dir, f = os.path.split(verfile)
143     try:
144         os.makedirs(dir)
145     except OSError:
146         pass    # okay if the directory already exists
147     open(verfile, "w").write("""<!--
148 THIS IS AN AUTOMATICALLY-GENERATED FILE.  DO NOT EDIT.
149 -->
150 <!ENTITY builddate "%s">
151 <!ENTITY buildversion "%s">
152 <!ENTITY buildrevision "%s">
153 """ % (date, ver, rev))
154
155     builders_gen = os.path.join(build, 'user', 'builders.gen')
156     builders_mod = os.path.join(build, 'user', 'builders.mod')
157     tools_gen = os.path.join(build, 'user', 'tools.gen')
158     tools_mod = os.path.join(build, 'user', 'tools.mod')
159     variables_gen = os.path.join(build, 'user', 'variables.gen')
160     variables_mod = os.path.join(build, 'user', 'variables.mod')
161
162     # We put $( - $) around $SOURCES in the command line below because
163     # the path names will change when a given input file is found in
164     # a repository one run and locally the next, and we don't want
165     # to rebuild documentation just because it's found in one location
166     # vs. the other.  The *.gen and *.mod targets will still be dependent
167     # on the list of the files themselves.
168     doc_output_files = [builders_gen, builders_mod,
169                         tools_gen, tools_mod,
170                         variables_gen, variables_mod]
171     b = env.Command(doc_output_files,
172                     scons_doc_files,
173                     "$PYTHON $SCONS_PROC_PY --sgml -b ${TARGETS[0]},${TARGETS[1]} -t ${TARGETS[2]},${TARGETS[3]} -v ${TARGETS[4]},${TARGETS[5]} $( $SOURCES $)")
174     env.Depends(b, "$SCONS_PROC_PY")
175
176     env.Local(b)
177
178     #
179     # Each document will live in its own subdirectory.  List them here
180     # as hash keys, with a hash of the info to control its build.
181     #
182     docs = {
183         'design' : {
184                 'htmlindex' : 'book1.html',
185                 'ps'        : 1,
186                 'pdf'       : 1,
187                 'text'      : 0,
188         },
189         # This doesn't build on all systems, and the document is old
190         # enough that there's reallyno need to build it every time any
191         # more, so just comment it out for now.
192         #'python10' : {
193         #        'htmlindex' : 't1.html',
194         #        'html'      : 1,
195         #        'ps'        : 1,
196         #        'pdf'       : 0,
197         #        'text'      : 0,
198         #        'graphics'  : [
199         #                        'arch.fig',
200         #                        'builder.fig',
201         #                        'job-task.fig',
202         #                        'node.fig',
203         #                        'scanner.fig',
204         #                        'sig.fig'
205         #                      ],
206         #},
207         'reference' : {
208                 'htmlindex' : 'book1.html',
209                 'html'      : 1,
210                 'ps'        : 1,
211                 'pdf'       : 1,
212                 'text'      : 0,
213         },
214         # For whenever (if ever?) we start putting developer guide
215         # information in a printable document instead of the wiki.
216         #'developer' : {
217         #        'htmlindex' : 'book1.html',
218         #        'html'      : 1,
219         #        'ps'        : 1,
220         #        'pdf'       : 1,
221         #        'text'      : 0,
222         #},
223         'user' : {
224                 'htmlindex' : 'book1.html',
225                 'html'      : 1,
226                 'ps'        : 1,
227                 'pdf'       : 1,
228                 'text'      : 1,
229                 'graphics'  : [
230                                 'SCons-win32-install-1.jpg',
231                                 'SCons-win32-install-2.jpg',
232                                 'SCons-win32-install-3.jpg',
233                                 'SCons-win32-install-4.jpg',
234                               ],
235                 'sconsoutput' : 1,
236         },
237     }
238
239     #
240     # We have to tell SCons to scan the top-level SGML files which
241     # get included by the document SGML files in the subdirectories.
242     #
243     manifest = File('MANIFEST').rstr()
244     src_files = map(lambda x: x[:-1], open(manifest).readlines())
245     for s in src_files:
246         base, ext = os.path.splitext(s)
247         if ext in ['.fig', '.jpg']:
248             orig_env.Install(build, s)
249         else:
250             orig_env.SCons_revision(os.path.join(build, s), s)
251         Local(os.path.join(build, s))
252
253     #
254     # For each document, build the document itself in HTML, Postscript,
255     # and PDF formats.
256     #
257     for doc in docs.keys():
258         manifest = File(os.path.join(doc, 'MANIFEST')).rstr()
259         src_files = map(lambda x: x[:-1],
260                         open(manifest).readlines())
261         build_doc = docs[doc].get('sconsoutput') and int(ARGUMENTS.get('BUILDDOC', 0))
262         for s in src_files:
263             doc_s = os.path.join(doc, s)
264             build_s = os.path.join(build, doc, s)
265             base, ext = os.path.splitext(doc_s)
266             if ext in ['.fig', '.jpg']:
267                 orig_env.InstallAs(build_s, doc_s)
268             else:
269                 if build_doc and ext == '.sgml':
270                     env.Command(doc_s,
271                                 base + '.in',
272                                 "$PYTHON $SCONSOUTPUT_PY $SOURCE > $TARGET")
273                 orig_env.SCons_revision(build_s, doc_s)
274             Local(build_s)
275
276         main = os.path.join(build, doc, 'main.sgml')
277         out = 'main.out'
278
279         # Hard-coding the scons-src path is a bit of a hack.  This can
280         # be reworked when a better solution presents itself.
281         scons_src_main = os.path.join(build_dir, 'scons-src', 'doc', main)
282         env.Ignore(scons_src_main, version_sgml)
283
284         htmldir = os.path.join(build, 'HTML', 'scons-%s' % doc)
285         htmlindex = os.path.join(htmldir, docs[doc]['htmlindex'])
286         html = os.path.join(build, 'HTML', 'scons-%s.html' % doc)
287         ps = os.path.join(build, 'PS', 'scons-%s.ps' % doc)
288         pdf = os.path.join(build, 'PDF', 'scons-%s.pdf' % doc)
289         text = os.path.join(build, 'TEXT', 'scons-%s.txt' % doc)
290
291         if docs[doc].get('html') and jade:
292             cmds = [
293                 Delete("${TARGET.dir}/*.html"),
294                 "jw -b html -o ${TARGET.dir} $SOURCES",
295             ]
296             if tidy:
297                 cmds.append("tidy -m -q $TARGET || true")
298             env.Command(htmlindex, File(main), cmds)
299             Local(htmlindex)
300
301             cmds = [
302                 Delete("${TARGET.dir}/main.html"),
303                 "jw -u -b html -o ${TARGET.dir} $SOURCES",
304                 Move("$TARGET", "${TARGET.dir}/main.html"),
305             ]
306             if tidy:
307                 cmds.append("tidy -m -q $TARGET || true")
308             env.Command(html, File(main), cmds)
309             Local(html)
310
311             env.Ignore([html, htmlindex], version_sgml)
312
313             tar_deps.extend([html, htmlindex])
314             tar_list.extend([html, htmldir])
315
316             for g in docs[doc].get('graphics', []):
317                 base, ext = os.path.splitext(g)
318                 if ext == '.fig':
319                     jpg = base + '.jpg'
320                     htmldir_jpg = os.path.join(htmldir, jpg)
321                     if fig2dev:
322                         fig = os.path.join(build, doc, g)
323                         env.Command(htmldir_jpg, fig,
324                                     "%s -L jpeg -q 100 $SOURCES $TARGET" % fig2dev)
325                     else:
326                         env.InstallAs(htmldir_jpg, jpg)
327                     env.Depends(html, htmldir_jpg)
328                     Local(htmldir_jpg)
329                 else:
330                     src = os.path.join(build, doc, g)
331                     Local(env.Install(htmldir, src))
332
333         if docs[doc].get('ps') and jadetex:
334             env.Command(ps, main, [
335                 Delete("${TARGET.dir}/%s" % out),
336                 "jw -b ps -o ${TARGET.dir} $SOURCES",
337                 "mv ${TARGET.dir}/main.ps $TARGET",
338                 Delete("${TARGET.dir}/%s" % out),
339             ])
340             Local(ps)
341
342             env.Ignore(ps, version_sgml)
343
344             tar_deps.append(ps)
345             tar_list.append(ps)
346
347             for g in docs[doc].get('graphics', []):
348                 base, ext = os.path.splitext(g)
349                 if ext == '.fig':
350                     eps = base + '.eps'
351                     build_eps = os.path.join(build, 'PS', eps)
352                     if fig2dev:
353                         fig = os.path.join(build, doc, g)
354                         env.Command(build_eps, fig, "%s -L eps $SOURCES $TARGET" % fig2dev)
355                     else:
356                         env.InstallAs(build_eps, eps)
357                     env.Depends(ps, build_eps)
358                     Local(build_eps)
359                 else:
360                     src = os.path.join(build, doc, g)
361                     Local(env.Install(htmldir, src))
362
363         if docs[doc].get('pdf') and pdfjadetex:
364             env.Command(pdf, main, [
365                 Delete("${TARGET.dir}/%s" % out),
366                 "jw -b pdf -o ${TARGET.dir} $SOURCES",
367                 "mv ${TARGET.dir}/main.pdf $TARGET",
368                 Delete("${TARGET.dir}/out"),
369             ])
370             Local(pdf)
371
372             env.Ignore(pdf, version_sgml)
373
374             tar_deps.append(pdf)
375             tar_list.append(pdf)
376
377         if docs[doc].get('text') and jade and lynx:
378             env.Command(text, html, "lynx -dump ${SOURCE.abspath} > $TARGET")
379             Local(text)
380
381             env.Ignore(text, version_sgml)
382
383             tar_deps.append(text)
384             tar_list.append(text)
385
386 #
387 # Man page(s), in good ol' troff format.
388 #
389 man_page_list = ['scons.1', 'sconsign.1', 'scons-time.1']
390
391 for m in man_page_list:
392     x = orig_env.SCons_revision(os.path.join(build, 'man', m),
393                             os.path.join('man', m))
394
395 man_i_files = ['builders.man', 'tools.man', 'variables.man']
396
397 man_intermediate_files = map(lambda x: os.path.join(build, 'man', x),
398                              man_i_files)
399
400 cmd = "$PYTHON $SCONS_PROC_PY --man -b ${TARGETS[0]} -t ${TARGETS[1]} -v ${TARGETS[2]} $( $SOURCES $)"
401 man_intermediate_files = env.Command(man_intermediate_files,
402                                      scons_doc_files,
403                                      cmd)
404 env.Depends(man_intermediate_files, "$SCONS_PROC_PY")
405 Local(man_intermediate_files)
406
407 for man_1 in man_page_list:
408     man, _1 = os.path.splitext(man_1)
409
410     man_1 = os.path.join(build, 'man', man_1)
411
412     if groff:
413         ps = os.path.join(build, 'PS', '%s-man.ps' % man)
414         text = os.path.join(build, 'TEXT', '%s-man.txt' % man)
415
416         b = env.Command(ps, man_1, "( cd ${SOURCES.dir} && groff -man -Tps ${SOURCES.file} ) > $TARGET")
417         Local(ps)
418         env.Depends(b, man_intermediate_files)
419
420         b = env.Command(text, man_1, "( cd ${SOURCES.dir} && groff -man -Tascii ${SOURCES.file} ) > $TARGET")
421         Local(text)
422         env.Depends(b, man_intermediate_files)
423
424         tar_deps.extend([ps, text])
425         tar_list.extend([ps, text])
426
427     if man2html:
428         html = os.path.join(build, 'HTML' , '%s-man.html' % man)
429
430         def strip_to_first_html_tag(target, source, env):
431             t = str(target[0])
432             contents = open(t).read()
433             contents = contents[string.find(contents, '<HTML>'):]
434             open(t, 'w').write(contents)
435             return 0
436
437         cmds = [
438             "( cd %s/man && cp %s .. )" % (build, string.join(man_i_files)),
439             "( cd ${SOURCE.dir} && man2html ${SOURCE.file} ) > $TARGET",
440             Action(strip_to_first_html_tag),
441         ]
442         if tidy:
443             cmds.append("tidy -m -q $TARGET || true")
444         b = env.Command(html, man_1, cmds)
445         Local(html)
446         env.Depends(b, man_intermediate_files)
447
448         tar_deps.append(html)
449         tar_list.append(html)
450
451 if epydoc:
452     # XXX Should be in common with reading the same thing in
453     # the SConstruct file.
454     e = os.path.join('#src', 'engine')
455     manifest_in = File(os.path.join(e, 'MANIFEST.in')).rstr()
456     sources = map(lambda x: x[:-1], open(manifest_in).readlines())
457     sources = filter(lambda x: string.find(x, 'Optik') == -1, sources)
458     sources = filter(lambda x: string.find(x, 'Platform') == -1, sources)
459     sources = filter(lambda x: string.find(x, 'Tool') == -1, sources)
460     # XXX
461     sources = filter(lambda x: string.find(x, 'Options') == -1, sources)
462
463     e = os.path.join(build, '..', 'scons', 'engine')
464     sources = map(lambda x, e=e: os.path.join(e, x), sources)
465
466     epydoc_commands = [
467         Delete('$OUTDIR'),
468         '$EPYDOC $EPYDOCFLAGS --output $OUTDIR --docformat=restructuredText --name SCons --url http://www.scons.org/ $SOURCES',
469         Touch('$TARGET'),
470     ]
471
472     htmldir = os.path.join(build, 'HTML', 'scons-api')
473     env.Command('${OUTDIR}/index.html', sources, epydoc_commands,
474                 EPYDOC=epydoc, EPYDOCFLAGS='--html', OUTDIR=htmldir)
475     tar_deps.append(htmldir)
476     tar_list.append(htmldir)
477
478     # PDF and PostScript and TeX are built from the
479     # same invocation.
480     api_dir = os.path.join(build, 'scons-api')
481     api_pdf = os.path.join(api_dir, 'api.pdf')
482     api_ps = os.path.join(api_dir, 'api.ps')
483     api_tex = os.path.join(api_dir, 'api.tex')
484     api_targets = [api_pdf, api_ps, api_tex]
485     env.Command(api_targets, sources, epydoc_commands,
486                 EPYDOC=epydoc, EPYDOCFLAGS='--pdf', OUTDIR=api_dir)
487     Local(api_targets)
488
489     pdf_install = os.path.join(build, 'PDF', 'scons-api.pdf')
490     env.InstallAs(pdf_install, api_pdf)
491     tar_deps.append(pdf_install)
492     tar_list.append(pdf_install)
493     Local(pdf_install)
494
495     ps_install = os.path.join(build, 'PS', 'scons-api.ps')
496     env.InstallAs(ps_install, api_ps)
497     tar_deps.append(ps_install)
498     tar_list.append(ps_install)
499     Local(ps_install)
500
501 #
502 # Now actually create the tar file of the documentation,
503 # for easy distribution to the web site.
504 #
505 if tar_deps:
506     tar_list = string.join(map(lambda x, b=build+'/': string.replace(x, b, ''),
507                            tar_list))
508     env.Command(doc_tar_gz, tar_deps,
509                 "tar cf${TAR_HFLAG} - -C %s %s | gzip > $TARGET" % (build, tar_list))
510     Local(doc_tar_gz)