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