]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
*** empty log message ***
[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', 'Python.h')
334         for i in headers:
335                 if conf.CheckCHeader (i):
336                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
337                         conf.env['DEFINES'][key] = 1
338
339         ccheaders = ('sstream',)
340         for i in ccheaders:
341                 if conf.CheckCXXHeader (i):
342                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
343                         conf.env['DEFINES'][key] = 1
344
345         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
346         for i in functions:
347                 if 0 or conf.CheckFunc (i):
348                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
349                         conf.env['DEFINES'][key] = 1
350
351         if conf.CheckYYCurrentBuffer ():
352                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
353
354         if conf.CheckLib ('dl'):
355                 pass
356
357         if conf.CheckLib ('kpathsea'):
358                 conf.env['DEFINES']['KPATHSEA'] = 1
359
360         # huh? 
361         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
362                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
363         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
364                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
365
366         if env['fast']:
367                 cpppath = []
368                 if env.has_key ('CPPPATH'):
369                         cpppath = env['CPPPATH']
370         #this could happen after flower...
371         env.ParseConfig ('guile-config compile')
372
373         #this could happen only for compiling pango-*
374         if env['gui']:
375                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
376                 env.ParseConfig ('pkg-config --cflags --libs pango')
377                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
378                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
379
380                 if conf.CheckLib ('pango-1.0',
381                                   'pango_fc_font_map_add_decoder_find_func'):
382                         conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
383                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
384
385         if env['fast']:
386                 # Using CCFLAGS = -I<system-dir> rather than CPPPATH = [
387                 # <system-dir>] speeds up SCons
388                 print 'cpp', env['CPPPATH']
389                 env['CCFLAGS'] += map (lambda x: '-I' + x,
390                                        env['CPPPATH'][len (cpppath):])
391                 env['CPPPATH'] = cpppath
392
393         if required:
394 F               print
395                 print '********************************'
396                 print 'Please install required packages'
397                 for i in required:
398                         print '%s:      %s-%s or newer (found: %s %s)' % i
399                 Exit (1)
400
401         if optional:
402                 print
403                 print '*************************************'
404                 print 'Consider installing optional packages'
405                 for i in optional:
406                         print '%s:      %s-%s or newer (found: %s %s)' % i
407
408         return conf.Finish ()
409
410 def config_header (target, source, env):
411         config = open (str (target[0]), 'w')
412         for i in list_sort (env['DEFINES'].keys ()):
413                 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
414         config.close ()
415 env.Command (config_hh, config_cache, config_header)
416
417 # hmm?
418 def xuniquify (lst):
419         n = []
420         for i in lst:
421                 if not i in n:
422                         n.append (i)
423         lst = n
424         return lst
425
426 def uniquify (lst):
427         d = {}
428         n = len (lst)
429         i = 0
430         while i < n:
431                 if not d.has_key (lst[i]):
432                         d[lst[i]] = 1
433                         i += 1
434                 else:
435                         del lst[i]
436                         n -= 1
437         return lst
438
439 def uniquify_config_vars (env):
440         for i in config_vars:
441                 if env.has_key (i) and type (env[i]) == type ([]):
442                         env[i] = uniquify (env[i])
443
444 def save_config_cache (env):
445         ## FIXME: Is this smart, using option cache for saving
446         ## config.cache?  I cannot seem to find the official method.
447         uniquify_config_vars (env)
448         opts.Save (config_cache, env)
449
450         if 'config' in COMMAND_LINE_TARGETS:
451                 sys.stdout.write ('\n')
452                 sys.stdout.write ('LilyPond configured')
453                 sys.stdout.write ('\n')
454                 sys.stdout.write ('now run')
455                 sys.stdout.write ('\n')
456                 sys.stdout.write ('    scons [TARGET|DIR]...')
457                 sys.stdout.write ('\n')
458                 Exit (0)
459         elif not env['checksums']:
460                 # When using timestams, config.hh is NEW.  The next
461                 # build triggers recompilation of everything.  Exiting
462                 # here makes SCons use the actual timestamp for config.hh
463                 # and prevents recompiling everything the next run.
464                 command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS)
465                 sys.stdout.write ('Running %s ... ' % command)
466                 sys.stdout.write ('\n')
467                 s = os.system (command)
468                 Exit (s)
469
470
471 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
472         os.unlink (config_cache)
473 # WTF?
474 # scons: *** Calling Configure from Builders is not supported.
475 # env.Command (config_cache, None, configure)
476 if not os.path.exists (config_cache) \
477    or (os.stat ('SConstruct')[stat.ST_MTIME]
478        > os.stat (config_cache)[stat.ST_MTIME]):
479         env = configure (None, None, env)
480         save_config_cache (env)
481 elif env['checksums']:
482         # just save everything
483         save_config_cache (env)
484
485 #urg how does #/ subst work?
486 Export ('env')
487 SConscript ('buildscripts/builder.py')
488
489 env.PrependENVPath ('PATH',
490                     os.path.join (env['absbuild'], env['out'], 'usr/bin'))
491
492 if os.environ.has_key ('TEXMF'):
493         env.Append (ENV = {'TEXMF' : os.environ['TEXMF']})
494 env.Append (ENV = {
495         'TEXMF' : '{$LILYPONDPREFIX,' \
496         + os.popen ('kpsexpand \$TEXMF').read ()[:-1] + '}',
497         'LILYPONDPREFIX' : os.path.join (run_prefix, 'share/lilypond'),
498         })
499
500 BUILD_ABC2LY = '${set__x}$PYTHON $srcdir/scripts/abc2ly.py'
501 BUILD_LILYPOND = '${set__x}$PYTHON $srcdir/scripts/lilypond.py${__verbose}'
502 BUILD_LILYPOND_BIN = '$absbuild/$out/lilypond-bin ${__verbose}'
503 BUILD_LILYPOND_BOOK = '$PYTHON $srcdir/scripts/lilypond-book.py --verbose'
504
505
506 # post-option environment-update
507 env.Append (
508         bindir = bindir,
509         sharedir = sharedir,
510         lilypond_datadir = sharedir_package,
511         localedir = localedir,
512         local_lilypond_datadir = sharedir_package_version,
513         lilypondprefix = lilypondprefix,
514         sharedir_package = sharedir_package,
515         sharedir_doc_package = sharedir_doc_package,
516         sharedir_package_version = sharedir_package_version,
517
518         LILYPOND = BUILD_LILYPOND,
519         ABC2LY = BUILD_ABC2LY,
520         LILYPOND_BOOK = BUILD_LILYPOND_BOOK,
521         LILYPOND_BOOK_FORMAT = 'texi-html',
522         MAKEINFO_FLAGS = '--css-include=$srcdir/Documentation/texinfo.css',
523
524         TEXI2DVI_PAPERSIZE = '@afourpaper',
525         TEXI2DVI_FLAGS = [ '-t $TEXI2DVI_PAPERSIZE'],
526         DVIPS_PAPERSIZE = 'a4',
527         DVIPS_FLAGS = ['-t $DVIPS_PAPERSIZE',
528                        '-u+lilypond.map',
529                        '-u+ec-mftrace.map'],
530         PSPDF_FLAGS = ['-sPAPERSIZE=$DVIPS_PAPERSIZE'],
531         )
532
533 if env['debugging']:
534         env.Append (CCFLAGS = ['-g', '-pipe'])
535 if env['optimising']:
536         env.Append (CCFLAGS = '-O2')
537         env.Append (CXXFLAGS = ['-DSTRING_UTILS_INLINED'])
538 if env['warnings']:
539         env.Append (CCFLAGS = ['-W', '-Wall'])
540         env.Append (CXXFLAGS = ['-Wconversion'])
541
542 # ugr,huh?
543 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
544
545 if env['verbose']:
546         env['__verbose'] = ' --verbose'
547         env['set__x'] = 'set -x;'
548
549
550 ## Explicit target and dependencies
551
552 if 'clean' in COMMAND_LINE_TARGETS:
553         # ugh: prevent reconfigure instead of clean
554         os.system ('touch %s' % config_cache)
555         
556         command = sys.argv[0] + ' -c .'
557         sys.stdout.write ('Running %s ... ' % command)
558         sys.stdout.write ('\n')
559         s = os.system (command)
560         if os.path.exists (config_cache):
561                 os.unlink (config_cache)
562         Exit (s)
563
564 if 'sconsclean' in COMMAND_LINE_TARGETS:
565         command = 'rm -rf scons.cache $(find . -name ".scon*")'
566         s = os.system (command)
567         if os.path.exists (config_cache):
568                 os.unlink (config_cache)
569         Exit (s)
570         
571 if 'realclean' in COMMAND_LINE_TARGETS:
572         command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
573         sys.stdout.write ('Running %s ... ' % command)
574         sys.stdout.write ('\n')
575         s = os.system (command)
576         if os.path.exists (config_cache):
577                 os.unlink (config_cache)
578         Exit (s)
579
580 # Declare SConscript phonies 
581 env.Alias ('minimal', config_cache)
582 env.Alias ('mf-essential', config_cache)
583
584 env.Alias ('minimal', ['lily', 'mf-essential'])
585 env.Alias ('all', ['minimal', 'mf', '.'])
586 # Do we want the doc/web separation?
587 env.Alias ('doc',
588            ['Documentation',
589             'Documentation/user',
590             'Documentation/topdocs',
591             'Documentation/bibliography',
592             'input'])
593
594 # Without target arguments, do minimal build
595 if not COMMAND_LINE_TARGETS:
596         env.Default (['minimal'])
597
598 # GNU Make rerouting compat:
599 env.Alias ('web', 'doc')
600
601
602 env.Command (version_hh, '#/VERSION',
603              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
604
605 # post-config environment update
606 env.Append (
607         run_prefix = run_prefix,
608         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
609
610         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
611         CPPPATH = [outdir, ],
612         LILYPOND_PATH = ['.', '$srcdir/input',
613                          '$srcdir/input/regression',
614                          '$srcdir/input/test',
615                          '$srcdir/input/tutorial',
616                          '$srcdir/Documentation/user',
617                          '$absbuild/mf/$out',
618 #                        os.path.join (absbuild, 'Documentation',
619 #                                      env['out']),
620 #                        os.path.join (absbuild, 'Documentation/user',
621 #                                      env['out']),
622                          ],
623         MAKEINFO_PATH = ['.', '$srcdir/Documentation/user',
624                          '$absbuild/Documentation/user/$out'],
625         )
626
627 def symlink_tree (target, source, env):
628         def mkdirs (dir):
629                 def mkdir (dir):
630                         if not dir:
631                                 os.chdir (os.sep)
632                                 return
633                         if not os.path.isdir (dir):
634                                 if os.path.exists (dir):
635                                         os.unlink (dir)
636                                 os.mkdir (dir)
637                         os.chdir (dir)
638                 map (mkdir, string.split (dir, os.sep))
639         def symlink (src, dst):
640                 os.chdir (absbuild)
641                 dir = os.path.dirname (dst)
642                 mkdirs (dir)
643                 if src[0] == '#':
644                         frm = os.path.join (srcdir, src[1:])
645                 else:
646                         depth = len (string.split (dir, '/'))
647                         if src.find ('@') > -1:
648                                 frm = os.path.join ('../' * depth,
649                                                     string.replace (src, '@',
650                                                                     env['out']))
651                         else:
652                                 frm = os.path.join ('../' * depth, src,
653                                                     env['out'])
654                 if src[-1] == '/':
655                         frm = os.path.join (frm, os.path.basename (dst))
656                 if env['verbose']:
657                         print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
658                 os.symlink (frm, os.path.basename (dst))
659         shutil.rmtree (run_prefix)
660         prefix = os.path.join (env['out'], 'usr')
661         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
662              # ^# := source dir
663              # @  := out
664              # /$ := add dst file_name
665              (('python',     'lib/lilypond/python'),
666               ('lily/',      'bin/lilypond-bin'),
667               ('scripts/',   'bin/lilypond'),
668               ('scripts/',   'bin/lilypond-book'),
669               ('mf',         'share/lilypond/dvips'),
670               ('#ps',        'share/lilypond/tex/music-drawing-routines.ps'),
671               ('mf',         'share/lilypond/afm'),
672               ('mf',         'share/lilypond/tfm'),
673               ('#mf',        'share/lilypond/fonts/mf'),
674               ('mf',         'share/lilypond/fonts/afm'),
675               ('mf',         'share/lilypond/fonts/tfm'),
676               ('mf',         'share/lilypond/fonts/type1'),
677               ('#tex',       'share/lilypond/tex/source'),
678               ('mf',         'share/lilypond/tex/generate'),
679               ('#ly',        'share/lilypond/ly'),
680               ('#scm',       'share/lilypond/scm'),
681               ('#ps',        'share/lilypond/ps'),
682               ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
683               ('elisp',      'share/lilypond/elisp')))
684         os.chdir (srcdir)
685
686 if env['debugging']:
687         stamp = os.path.join (run_prefix, 'stamp')
688         env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
689         env.Depends ('lily', stamp)
690         
691 #### dist, tar
692 def plus (a, b):
693         a + b
694
695 def cvs_entry_is_dir (line):
696         return line[0] == 'D' and line[-2] == '/'
697
698 def cvs_entry_is_file (line):
699         return line[0] == '/' and line[-2] == '/'
700
701 def cvs_dirs (dir):
702         ENTRIES = os.path.join (dir, 'CVS/Entries')
703         if not os.path.exists (ENTRIES):
704                 return []
705         entries = open (ENTRIES).readlines ()
706         dir_entries = filter (cvs_entry_is_dir, entries)
707         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
708                     dir_entries)
709         return dirs + map (cvs_dirs, dirs)
710
711 def cvs_files (dir):
712         ENTRIES = os.path.join (dir, 'CVS/Entries')
713         entries = open (ENTRIES).readlines ()
714         file_entries = filter (cvs_entry_is_file, entries)
715         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
716         return map (lambda x: os.path.join (dir, x), files)
717
718 def flatten (tree, lst):
719         if type (tree) == type ([]):
720                 for i in tree:
721                         if type (i) == type ([]):
722                                 flatten (i, lst)
723                         else:
724                                 lst.append (i)
725         return lst
726
727 if env['fast']\
728    and 'all' not in COMMAND_LINE_TARGETS\
729    and 'doc' not in COMMAND_LINE_TARGETS\
730    and 'web' not in COMMAND_LINE_TARGETS\
731    and 'install' not in COMMAND_LINE_TARGETS\
732    and 'clean' not in COMMAND_LINE_TARGETS:
733         subdirs = ['lily', 'lily/include', 'flower', 'flower/include', 'mf']
734 else:
735         subdirs = flatten (cvs_dirs ('.'), [])
736 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
737 foo = map (lambda x: env.TXT (x + '.txt',
738                               os.path.join ('Documentation/topdocs', x)),
739            readme_files)
740 txt_files = map (lambda x: x + '.txt', readme_files)
741 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
742 tar_base = package.name + '-' + version
743 tar_name = tar_base + '.tar.gz'
744 ball_prefix = os.path.join (outdir, tar_base)
745 tar_ball = os.path.join (outdir, tar_name)
746
747 dist_files = src_files + txt_files
748 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
749 map (lambda x: env.Depends (tar_ball, x), ball_files)
750 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
751                             'ln $SOURCE $TARGET'), dist_files)
752 tar = env.Command (tar_ball, src_files,
753                    ['rm -f $$(find $TARGET.dir -name .sconsign)',
754                     'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
755 env.Alias ('tar', tar)
756
757 dist_ball = os.path.join (package.release_dir, tar_name)
758 env.Command (dist_ball, tar_ball,
759              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
760              + 'ln $SOURCE $TARGET')
761 env.Depends ('dist', dist_ball)
762 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
763 patch = env.PATCH (patch_name, tar_ball)
764 env.Depends (patch_name, dist_ball)
765 env.Alias ('release', patch)
766
767 #### web
768 web_base = os.path.join (outdir, 'web')
769 web_ball = web_base + '.tar.gz'
770 env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
771 web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
772 web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"')
773 env['web_path'] = web_path
774 web_list = os.path.join (outdir, 'weblist')
775 # compatible make heritits
776 # fixme: generate in $outdir is cwd/builddir
777 env.Command (web_list,
778              ## this is correct, but takes > 5min if you have a peder :-)
779              ##'doc',
780              '#/VERSION',
781              ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
782               'cd $absbuild && $footify $$(find . -name "*.html" -print)',
783               'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
784               'cd $absbuild && find Documentation input $web_path \
785               > $TARGET',
786               '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
787               '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
788               'cd $absbuild && ls *.html >> $TARGET',])
789 env.Command (web_ball, web_list,
790              ['cat $SOURCE | tar -C $absbuild -czf $TARGET -T -',])
791 #env.Alias ('web', web_ball)
792 www_base = os.path.join (outdir, 'www')
793 www_ball = www_base + '.tar.gz'
794 env.Command (www_ball, web_ball,
795              ['rm -rf $out/tmp',
796               'mkdir -p $absbuild/$out/tmp',
797               'tar -C $absbuild/$out/tmp -xzf $SOURCE',
798               'cd $absbuild/$out/tmp && for i in $$(find . -name "$out"); do mv $$i $$(dirname $$i)/out-www; done',
799               'tar -C $absbuild/$out/tmp -czf $TARGET .'])
800 env.Alias ('web', www_ball)
801
802 #### tags
803 env.Append (
804         ETAGSFLAGS = """--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\\1/' \
805         --regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\\1/'""")
806 code_ext = ['.cc', '.hh', '.scm', '.tcc',]
807 env.Command ('TAGS', filter (lambda x: os.path.splitext (x)[1] in code_ext,
808                              src_files),
809              'etags $ETAGSFLAGS $SOURCES')
810
811 # Note: SConscripts are only needed in directories where something needs
812 # to be done, building or installing
813 for d in subdirs:
814         if os.path.exists (os.path.join (d, 'SConscript')):
815                 b = os.path.join (env['build'], d, env['out'])
816                 # Support clean sourcetree build (--srcdir build)
817                 # and ./out build.
818                 if os.path.abspath (b) != os.path.abspath (d):
819                         env.BuildDir (b, d, duplicate = 0)
820                 SConscript (os.path.join (b, 'SConscript'))
821