]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
(web_kluts): Have make web build in out-www. Not.
[lilypond.git] / SConstruct
1 # -*-python-*-
2
3 '''
4 Experimental scons (www.scons.org) building.
5
6 Usage
7
8     scons TARGET
9
10 build from source directory ./TARGET (not recursive)
11
12 Configure, build
13
14     scons [config]             # configure
15     scons                      # build all
16
17 Run from build tree
18
19     run=$(pwd)/out-scons/usr
20     export LOCALE=$run/share/locale
21     export TEXMF='{'$run/share/lilypond,$(kpsexpand '$TEXMF')'}'
22     PATH=$run/bin:$PATH
23
24     #optionally, if you do not use custom.py below
25     #export LILYPONDPREFIX=$run/share/lilypond
26
27     lilypond-bin input/simple
28
29 Other targets
30     scons mf-essential         # build minimal mf stuff
31
32     scons doc                  # build web doc
33     scons config               # reconfigure
34     scons install              # install
35     scons -c                   # clean
36     scons -h                   # help
37
38     scons /                    # build *everything* (including installation)
39
40 Options  (see scons -h)
41     scons build=DIR            # clean srcdir build, output below DIR
42     scons out=DIR              # write output for alterative config to DIR
43
44 Debugging
45     scons --debug=dtree
46     scons --debug=explain
47     scons verbose=1
48
49 Optional custom.py
50
51 import os
52 out='out-scons'
53 optimising=0
54 debugging=1
55 gui=1
56 os.path.join (os.getcwd (), '=install')
57 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
58
59 '''
60
61
62 # TODO:
63 #  * install doc
64
65 #  * more program configure tests (mfont, ...?)
66
67 #  * split doc target: doc input examples mutopia?
68
69 #  * more fine-grained config.hh -- move lilypondprefix to version.hh?
70 #    - config.hh:   changes after system upgrades, affects all files
71 #    - version.hh:  prefix, version etc?  affects few
72
73 #    - what about GUILE_*_VERSION, seems to be the major culprit,
74 #      for config.hh dependency escalation.  Is the lily-guile.hh
75 #      workaround necessary at all for GUILE > 1.5?
76
77 #  * grep FIXME $(find . -name 'S*t')
78
79
80 import re
81 import glob
82 import os
83 import string
84 import sys
85 import stat
86 import shutil
87
88 # duh, we need 0.95.1
89 EnsureSConsVersion (0, 95)
90
91 usage = r'''Usage:
92 scons [KEY=VALUE].. [TARGET|DIR]..
93
94 TARGETS: clean, config, doc, dist, install, mf-essential, po-update,
95          realclean, release, tar, TAGS
96
97 '''
98       
99
100 config_cache = 'scons.cache'
101
102 config_vars = [
103         'BASH',
104         'CCFLAGS',
105         'CPPPATH',
106         'CPPDEFINES',
107         'CXXFLAGS',
108         'DEFINES',
109         'LIBS',
110         'LINKFLAGS',
111         'METAFONT',
112         'PERL',
113         'PYTHON',
114         ]
115
116 # Put your favourite stuff in custom.py
117 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
118 opts.Add ('prefix', 'Install prefix', '/usr/')
119 opts.Add ('out', 'Output directory', 'out-scons')
120 opts.Add ('build', 'Build directory', '.')
121 opts.Add ('DESTDIR', 'DESTDIR prepended to prefix', '')
122 opts.AddOptions (
123         BoolOption ('warnings', 'compile with -Wall and similiar',
124                    1),
125         BoolOption ('debugging', 'compile with debugging symbols',
126                     0),
127         BoolOption ('optimising', 'compile with optimising',
128                     1),
129         BoolOption ('shared', 'build shared libraries',
130                     0),
131         BoolOption ('static', 'build static libraries',
132                     1),
133         BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
134                     1),
135         BoolOption ('verbose', 'run commands with verbose flag',
136                     0),
137         BoolOption ('checksums', 'use checksums instead of timestamps',
138                     1),
139         )
140
141 srcdir = Dir ('.').srcnode ().abspath
142 #ugh
143 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
144 import packagepython
145 package = packagepython.Package (srcdir)
146 version = packagepython.version_tuple_to_str (package.version)
147
148 ENV = { 'PATH' : os.environ['PATH'] }
149 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
150         if os.environ.has_key (key):
151                 ENV[key] = os.environ[key]
152
153 env = Environment (
154         ENV = ENV,
155
156         BASH = '/bin/bash',
157         PERL = '/usr/bin/perl',
158         PYTHON = '/usr/bin/python',
159         SH = '/bin/sh',
160
161         MAKEINFO = 'LANG= makeinfo',
162         MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
163         
164         PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
165                                          'usr/pkg/gnome/lib'),
166                            os.path.join (os.environ['HOME'],
167                                          'usr/pkg/pango/lib')],
168         GZIP='-9v',
169         MFMODE = 'ljfour',
170         TOPLEVEL_VERSION = version,
171         )
172
173 # Add all config_vars to opts, so that they will be read and saved
174 # together with the other configure options.
175 map (lambda x: opts.AddOptions ((x,)), config_vars)
176
177 Help (usage + opts.GenerateHelpText (env))
178
179 opts.Update (env)
180
181 # Using content checksums prevents rebuilds after [re]configure if
182 # config.hh has not changed.
183 if env['checksums']:
184         SetOption ('max_drift', 0)
185         TargetSignatures ("content")
186
187 # build web in out-www, so that scons is a drop-in replacement for make
188 # we can revise the entire web building when web is built with scons.
189 web_kluts = ''
190 # Hmm:  scons: *** maximum recursion limit exceeded
191 if 0 and 'web' in COMMAND_LINE_TARGETS:
192         web_kluts = 'out-scons' #env['out']
193         env['out'] = 'out-www'
194
195 absbuild = Dir (env['build']).abspath
196 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
197 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
198
199
200 config_hh = os.path.join (outdir, 'config.hh')
201 version_hh = os.path.join (outdir, 'version.hh')
202
203 env.Alias ('config', config_cache)
204
205
206 CacheDir (os.path.join (outdir, 'build-cache'))
207
208 # No need to set $LILYPONDPREFIX to run lily, but cannot install...
209 if env['debugging'] and not 'install' in COMMAND_LINE_TARGETS:
210         env['prefix'] = run_prefix
211
212 prefix = env['prefix']
213 bindir = os.path.join (prefix, 'bin')
214 sharedir = os.path.join (prefix, 'share')
215 libdir = os.path.join (prefix, 'lib')
216 localedir = os.path.join (sharedir, 'locale')
217 sharedir_doc_package = os.path.join (sharedir, 'doc', package.name)
218 sharedir_package = os.path.join (sharedir, package.name)
219 sharedir_package_version = os.path.join (sharedir_package, version)
220 lilypondprefix = sharedir_package_version
221
222 # junkme
223 env.Append (
224         absbuild = absbuild,
225         srcdir = srcdir,
226         )
227
228
229 def list_sort (lst):
230         sorted = lst
231         sorted.sort ()
232         return sorted
233
234
235 def configure (target, source, env):
236         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
237         def get_version (program):
238                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
239                 pipe = os.popen (command)
240                 output = pipe.read ()
241                 if pipe.close ():
242                         return None
243                 v = re.sub (vre, '\\1', output)
244                 return string.split (v, '.')
245
246         def test_program (lst, program, minimal, description, package):
247                 sys.stdout.write ('Checking %s version... ' % program)
248                 actual = get_version (program)
249                 if not actual:
250                         print 'not found'
251                         lst.append ((description, package, minimal, program,
252                                      'not installed'))
253                         return
254                 sys.stdout.write (string.join (actual, '.'))
255                 sys.stdout.write ('\n')
256                 if actual < string.split (minimal, '.'):
257                         lst.append ((description, package, minimal, program,
258                                      string.join (actual, '.')))
259
260         for i in ['bash', 'perl', 'python', 'sh']:
261                 sys.stdout.write ('Checking for %s... ' % i)
262                 c = WhereIs (i)
263                 key = string.upper (i)
264                 if c:
265                         env[key] = c
266                         sys.stdout.write (c)
267                 else:
268                         sys.stdout.write ('not found: %s (using: %s)' \
269                                           % (c, env[key]))
270                         # Hmm? abort?
271                 sys.stdout.write ('\n')
272
273         required = []
274         test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
275         test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
276         test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
277         test_program (required, 'guile-config', '1.6', 'GUILE development',
278                         'libguile-dev or guile-devel')
279         # Do not use bison 1.50 and 1.75.
280         test_program (required, 'bison', '1.25', 'Bison -- parser generator',
281                         'bison')
282         test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
283
284
285         optional = []
286         test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
287         test_program (optional, 'guile', '1.6', 'GUILE scheme',
288                         'libguile-dev or guile-devel')
289         test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
290                         'mftrace')
291         test_program (optional, 'perl', '4.0',
292                         'Perl practical efficient readonly language', 'perl')
293         #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
294
295         def CheckYYCurrentBuffer (context):
296                 context.Message ('Checking for yy_current_buffer... ')
297                 ret = conf.TryCompile ("""using namespace std;
298                 #include <FlexLexer.h>
299                 class yy_flex_lexer: public yyFlexLexer
300                 {
301                 public:
302                 yy_flex_lexer ()
303                 {
304                 yy_current_buffer = 0;
305                 }
306                 };""", '.cc')
307                 context.Result (ret)
308                 return ret
309
310         conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
311                                                 : CheckYYCurrentBuffer })
312
313         defines = {
314            'DIRSEP' : "'%s'" % os.sep,
315            'PATHSEP' : "'%s'" % os.pathsep,
316            'TOPLEVEL_VERSION' : '"' + version + '"',
317            'PACKAGE': '"' + package.name + '"',
318            'DATADIR' : '"' + sharedir + '"',
319            'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
320            'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
321            'LOCALEDIR' : '"' + localedir + '"',
322         }
323         conf.env.Append (DEFINES = defines)
324
325         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
326         PYTHON_INCLUDE = os.popen (command).read ()
327         env.Append (CPPPATH = PYTHON_INCLUDE)
328
329         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
330         for i in headers:
331                 if conf.CheckCHeader (i):
332                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
333                         conf.env['DEFINES'][key] = 1
334
335         ccheaders = ('sstream',)
336         for i in ccheaders:
337                 if conf.CheckCXXHeader (i):
338                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
339                         conf.env['DEFINES'][key] = 1
340
341         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
342         for i in functions:
343                 if 0 or conf.CheckFunc (i):
344                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
345                         conf.env['DEFINES'][key] = 1
346
347         if conf.CheckYYCurrentBuffer ():
348                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
349
350         if conf.CheckLib ('dl'):
351                 pass
352
353         if conf.CheckLib ('kpathsea'):
354                 conf.env['DEFINES']['KPATHSEA'] = 1
355
356         # huh? 
357         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
358                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
359         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
360                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
361
362         #this could happen after flower...
363         env.ParseConfig ('guile-config compile')
364
365         #this could happen only for compiling pango-*
366         if env['gui']:
367                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
368                 env.ParseConfig ('pkg-config --cflags --libs pango')
369                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
370                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
371
372                 if conf.CheckLib ('pango-1.0',
373                                   'pango_fc_font_map_add_decoder_find_func'):
374                         conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
375                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
376
377         if required:
378                 print
379                 print '********************************'
380                 print 'Please install required packages'
381                 for i in required:
382                         print '%s:      %s-%s or newer (found: %s %s)' % i
383                 Exit (1)
384
385         if optional:
386                 print
387                 print '*************************************'
388                 print 'Consider installing optional packages'
389                 for i in optional:
390                         print '%s:      %s-%s or newer (found: %s %s)' % i
391
392         return conf.Finish ()
393
394 def config_header (target, source, env):
395         config = open (str (target[0]), 'w')
396         for i in list_sort (env['DEFINES'].keys ()):
397                 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
398         config.close ()
399 env.Command (config_hh, config_cache, config_header)
400
401 # hmm?
402 def xuniquify (lst):
403         n = []
404         for i in lst:
405                 if not i in n:
406                         n.append (i)
407         lst = n
408         return lst
409
410 def uniquify (lst):
411         d = {}
412         n = len (lst)
413         i = 0
414         while i < n:
415                 if not d.has_key (lst[i]):
416                         d[lst[i]] = 1
417                         i += 1
418                 else:
419                         del lst[i]
420                         n -= 1
421         return lst
422
423 def uniquify_config_vars (env):
424         for i in config_vars:
425                 if env.has_key (i) and type (env[i]) == type ([]):
426                         env[i] = uniquify (env[i])
427
428 def save_config_cache (env):
429         ## FIXME: Is this smart, using option cache for saving
430         ## config.cache?  I cannot seem to find the official method.
431         uniquify_config_vars (env)
432         opts.Save (config_cache, env)
433
434         if 'config' in COMMAND_LINE_TARGETS:
435                 sys.stdout.write ('\n')
436                 sys.stdout.write ('LilyPond configured')
437                 sys.stdout.write ('\n')
438                 sys.stdout.write ('now run')
439                 sys.stdout.write ('\n')
440                 sys.stdout.write ('    scons [TARGET|DIR]...')
441                 sys.stdout.write ('\n')
442                 Exit (0)
443         elif not env['checksums']:
444                 # When using timestams, config.hh is NEW.  The next
445                 # build triggers recompilation of everything.  Exiting
446                 # here makes SCons use the actual timestamp for config.hh
447                 # and prevents recompiling everything the next run.
448                 command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS)
449                 sys.stdout.write ('Running %s ... ' % command)
450                 sys.stdout.write ('\n')
451                 s = os.system (command)
452                 Exit (s)
453
454
455 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
456         os.unlink (config_cache)
457 # WTF?
458 # scons: *** Calling Configure from Builders is not supported.
459 # env.Command (config_cache, None, configure)
460 if not os.path.exists (config_cache) \
461    or (os.stat ('SConstruct')[stat.ST_MTIME]
462        > os.stat (config_cache)[stat.ST_MTIME]):
463         env = configure (None, None, env)
464         save_config_cache (env)
465 elif env['checksums']:
466         # just save everything
467         save_config_cache (env)
468
469 #urg how does #/ subst work?
470 Export ('env')
471 SConscript ('buildscripts/builder.py')
472
473 env.PrependENVPath ('PATH',
474                     os.path.join (env['absbuild'], env['out'], 'usr/bin'))
475
476 if os.environ.has_key ('TEXMF'):
477         env.Append (ENV = {'TEXMF' : os.environ['TEXMF']})
478 env.Append (ENV = {
479         'TEXMF' : '{$LILYPONDPREFIX,' \
480         + os.popen ('kpsexpand \$TEXMF').read ()[:-1] + '}',
481         'LILYPONDPREFIX' : os.path.join (run_prefix, 'share/lilypond'),
482         })
483
484 BUILD_ABC2LY = '${set__x}$PYTHON $srcdir/scripts/abc2ly.py'
485 BUILD_LILYPOND = '${set__x}$PYTHON $srcdir/scripts/lilypond.py${__verbose}'
486 BUILD_LILYPOND_BIN = '$absbuild/$out/lilypond-bin ${__verbose}'
487 BUILD_LILYPOND_BOOK = '$PYTHON $srcdir/scripts/lilypond-book.py --verbose'
488
489
490 # post-option environment-update
491 env.Append (
492         bindir = bindir,
493         sharedir = sharedir,
494         lilypond_datadir = sharedir_package,
495         localedir = localedir,
496         local_lilypond_datadir = sharedir_package_version,
497         lilypondprefix = lilypondprefix,
498         sharedir_package = sharedir_package,
499         sharedir_doc_package = sharedir_doc_package,
500         sharedir_package_version = sharedir_package_version,
501
502         LILYPOND = BUILD_LILYPOND,
503         ABC2LY = BUILD_ABC2LY,
504         LILYPOND_BOOK = BUILD_LILYPOND_BOOK,
505         LILYPOND_BOOK_FORMAT = 'texi-html',
506         MAKEINFO_FLAGS = '--css-include=$srcdir/Documentation/texinfo.css',
507
508         TEXI2DVI_PAPERSIZE = '@afourpaper',
509         TEXI2DVI_FLAGS = [ '-t $TEXI2DVI_PAPERSIZE'],
510         DVIPS_PAPERSIZE = 'a4',
511         DVIPS_FLAGS = ['-t $DVIPS_PAPERSIZE',
512                        '-u+lilypond.map',
513                        '-u+ec-mftrace.map'],
514         PSPDF_FLAGS = ['-sPAPERSIZE=$DVIPS_PAPERSIZE'],
515         )
516
517 if env['debugging']:
518         env.Append (CCFLAGS = ['-g', '-pipe'])
519 if env['optimising']:
520         env.Append (CCFLAGS = '-O2')
521         env.Append (CXXFLAGS = ['-DSTRING_UTILS_INLINED'])
522 if env['warnings']:
523         env.Append (CCFLAGS = ['-W', '-Wall'])
524         env.Append (CXXFLAGS = ['-Wconversion'])
525
526 # ugr,huh?
527 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
528
529 if env['verbose']:
530         env['__verbose'] = ' --verbose'
531         env['set__x'] = 'set -x;'
532
533
534 ## Explicit target and dependencies
535
536 if 'clean' in COMMAND_LINE_TARGETS:
537         # ugh: prevent reconfigure instead of clean
538         os.system ('touch %s' % config_cache)
539         
540         command = sys.argv[0] + ' -c .'
541         sys.stdout.write ('Running %s ... ' % command)
542         sys.stdout.write ('\n')
543         s = os.system (command)
544         if os.path.exists (config_cache):
545                 os.unlink (config_cache)
546         Exit (s)
547
548 if 'realclean' in COMMAND_LINE_TARGETS:
549         command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
550         sys.stdout.write ('Running %s ... ' % command)
551         sys.stdout.write ('\n')
552         s = os.system (command)
553         if os.path.exists (config_cache):
554                 os.unlink (config_cache)
555         Exit (s)
556
557 # Declare SConscript phonies 
558 env.Alias ('minimal', config_cache)
559 env.Alias ('mf-essential', config_cache)
560
561 env.Alias ('minimal', ['lily', 'mf-essential'])
562 env.Alias ('all', ['minimal', 'mf', '.'])
563 # Do we want the doc/web separation?
564 env.Alias ('doc',
565            ['Documentation',
566             'Documentation/user',
567             'Documentation/topdocs',
568             'Documentation/bibliography',
569             'input'])
570
571 # Without target arguments, do minimal build
572 if not COMMAND_LINE_TARGETS:
573         env.Default (['minimal'])
574
575 # GNU Make rerouting compat:
576 env.Alias ('web', 'doc')
577
578
579 env.Command (version_hh, '#/VERSION',
580              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
581
582 # post-config environment update
583 env.Append (
584         run_prefix = run_prefix,
585         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
586
587         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
588         ##CPPPATH = [outdir, '#',], # do not read auto*'s header
589         CPPPATH = [outdir, ],
590         LILYPOND_PATH = ['.', '$srcdir/input',
591                          '$srcdir/input/regression',
592                          '$srcdir/input/test',
593                          '$srcdir/input/tutorial',
594                          '$srcdir/Documentation/user',
595                          '$absbuild/mf/$out',
596 #                        os.path.join (absbuild, 'Documentation',
597 #                                      env['out']),
598 #                        os.path.join (absbuild, 'Documentation/user',
599 #                                      env['out']),
600                          ],
601         MAKEINFO_PATH = ['.', '$srcdir/Documentation/user',
602                          '$absbuild/Documentation/user/$out'],
603         )
604
605 def symlink_tree (target, source, env):
606         def mkdirs (dir):
607                 def mkdir (dir):
608                         if not dir:
609                                 os.chdir (os.sep)
610                                 return
611                         if not os.path.isdir (dir):
612                                 if os.path.exists (dir):
613                                         os.unlink (dir)
614                                 os.mkdir (dir)
615                         os.chdir (dir)
616                 map (mkdir, string.split (dir, os.sep))
617         def symlink (src, dst):
618                 os.chdir (absbuild)
619                 dir = os.path.dirname (dst)
620                 mkdirs (dir)
621                 if src[0] == '#':
622                         frm = os.path.join (srcdir, src[1:])
623                 else:
624                         depth = len (string.split (dir, '/'))
625                         if src.find ('@') > -1:
626                                 frm = os.path.join ('../' * depth,
627                                                     string.replace (src, '@',
628                                                                     env['out']))
629                         else:
630                                 frm = os.path.join ('../' * depth, src,
631                                                     env['out'])
632                 if src[-1] == '/':
633                         frm = os.path.join (frm, os.path.basename (dst))
634                 if env['verbose']:
635                         print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
636                 os.symlink (frm, os.path.basename (dst))
637         shutil.rmtree (run_prefix)
638         prefix = os.path.join (env['out'], 'usr')
639         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
640              # ^# := source dir
641              # @  := out
642              # /$ := add dst file_name
643              (('python',     'lib/lilypond/python'),
644               ('lily/',      'bin/lilypond-bin'),
645               ('scripts/',   'bin/lilypond'),
646               ('scripts/',   'bin/lilypond-book'),
647               ('mf',         'share/lilypond/dvips'),
648               ('#ps',        'share/lilypond/tex/music-drawing-routines.ps'),
649               ('mf',         'share/lilypond/afm'),
650               ('mf',         'share/lilypond/tfm'),
651               ('#mf',        'share/lilypond/fonts/mf'),
652               ('mf',         'share/lilypond/fonts/afm'),
653               ('mf',         'share/lilypond/fonts/tfm'),
654               ('mf',         'share/lilypond/fonts/type1'),
655               ('#tex',       'share/lilypond/tex/source'),
656               ('mf',         'share/lilypond/tex/generate'),
657               ('#ly',        'share/lilypond/ly'),
658               ('#scm',       'share/lilypond/scm'),
659               ('#ps',        'share/lilypond/ps'),
660               ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
661               ('elisp',      'share/lilypond/elisp')))
662         os.chdir (srcdir)
663
664 if env['debugging']:
665         stamp = os.path.join (run_prefix, 'stamp')
666         env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
667         env.Depends ('lily', stamp)
668         
669 #### dist, tar
670 def plus (a, b):
671         a + b
672
673 def cvs_entry_is_dir (line):
674         return line[0] == 'D' and line[-2] == '/'
675
676 def cvs_entry_is_file (line):
677         return line[0] == '/' and line[-2] == '/'
678
679 def cvs_dirs (dir):
680         ENTRIES = os.path.join (dir, 'CVS/Entries')
681         if not os.path.exists (ENTRIES):
682                 return []
683         entries = open (ENTRIES).readlines ()
684         dir_entries = filter (cvs_entry_is_dir, entries)
685         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
686                     dir_entries)
687         return dirs + map (cvs_dirs, dirs)
688
689 def cvs_files (dir):
690         ENTRIES = os.path.join (dir, 'CVS/Entries')
691         entries = open (ENTRIES).readlines ()
692         file_entries = filter (cvs_entry_is_file, entries)
693         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
694         return map (lambda x: os.path.join (dir, x), files)
695
696 def flatten (tree, lst):
697         if type (tree) == type ([]):
698                 for i in tree:
699                         if type (i) == type ([]):
700                                 flatten (i, lst)
701                         else:
702                                 lst.append (i)
703         return lst
704
705 subdirs = flatten (cvs_dirs ('.'), [])
706 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
707 foo = map (lambda x: env.TXT (x + '.txt',
708                               os.path.join ('Documentation/topdocs', x)),
709            readme_files)
710 txt_files = map (lambda x: x + '.txt', readme_files)
711 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
712 tar_base = package.name + '-' + version
713 tar_name = tar_base + '.tar.gz'
714 ball_prefix = os.path.join (outdir, tar_base)
715 tar_ball = os.path.join (outdir, tar_name)
716
717 dist_files = src_files + txt_files
718 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
719 map (lambda x: env.Depends (tar_ball, x), ball_files)
720 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
721                             'ln $SOURCE $TARGET'), dist_files)
722 tar = env.Command (tar_ball, src_files,
723                    ['rm -f $$(find $TARGET.dir -name .sconsign)',
724                     'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
725 env.Alias ('tar', tar)
726
727 dist_ball = os.path.join (package.release_dir, tar_name)
728 env.Command (dist_ball, tar_ball,
729              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
730              + 'ln $SOURCE $TARGET')
731 env.Depends ('dist', dist_ball)
732 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
733 patch = env.PATCH (patch_name, tar_ball)
734 env.Depends (patch_name, dist_ball)
735 env.Alias ('release', patch)
736
737 #### web
738 web_base = os.path.join (outdir, 'web')
739 web_ball = web_base + '.tar.gz'
740 env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
741 web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
742 web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"')
743 env['web_path'] = web_path
744 web_list = os.path.join (outdir, 'weblist')
745 # compatible make heritits
746 # fixme: generate in $outdir is cwd/builddir
747 env.Command (web_list,
748              ## this is correct, but takes > 5min if you have a peder :-)
749              ##'doc',
750              '#/VERSION',
751              ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
752               'cd $absbuild && $footify $$(find . -name "*.html" -print)',
753               # uhg?
754               'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
755               'cd $absbuild && find Documentation input $web_path \
756               > $TARGET',
757               '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
758               '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
759               # UGHR?  all .html cruft in cwd goes into the web ball?
760               'cd $absbuild && ls *.html >> $TARGET',])
761 env.Command (web_ball, web_list,
762              ['cat $SOURCE | tar -C $absbuild -czf $TARGET -T -',])
763 env.Alias ('web', web_ball)
764 env.Alias ('roll-web', web_ball)
765
766 #### tags
767 env.Append (
768         ETAGSFLAGS = ["""--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\1/'""",
769                       """--regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\1/'"""])
770 # filter-out some files?
771 env.Command ('TAGS', src_files, 'etags $ETAGSFLAGS $SOURCES')
772
773
774 # Note: SConscripts are only needed in directories where something needs
775 # to be done, building or installing
776 for d in subdirs:
777         if os.path.exists (os.path.join (d, 'SConscript')):
778                 b = os.path.join (env['build'], d, env['out'])
779                 # Support clean sourcetree build (--srcdir build)
780                 # and ./out build.
781                 if os.path.abspath (b) != os.path.abspath (d):
782                         env.BuildDir (b, d, duplicate = 0)
783                 if web_kluts:
784                         # look in out-scons 
785                         env.Repository (os.path.join (env['build'], d,
786                                                       web_kluts))
787                 SConscript (os.path.join (b, 'SConscript'))
788