]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
GO_FAST_BUTTON: implement tips from Andreas Roach.
[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, 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                     1),
133         )
134
135 srcdir = Dir ('.').srcnode ().abspath
136 #ugh
137 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
138 import packagepython
139 package = packagepython.Package (srcdir)
140 version = packagepython.version_tuple_to_str (package.version)
141
142 ENV = { 'PATH' : os.environ['PATH'] }
143 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
144         if os.environ.has_key (key):
145                 ENV[key] = os.environ[key]
146
147 env = Environment (
148         ENV = ENV,
149
150         BASH = '/bin/bash',
151         PERL = '/usr/bin/perl',
152         PYTHON = '/usr/bin/python',
153         SH = '/bin/sh',
154
155         MAKEINFO = 'LANG= makeinfo',
156         MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
157         
158         PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
159                                          'usr/pkg/gnome/lib'),
160                            os.path.join (os.environ['HOME'],
161                                          'usr/pkg/pango/lib')],
162         GZIP='-9v',
163         MFMODE = 'ljfour',
164         TOPLEVEL_VERSION = version,
165         )
166
167 # Hardcoded usability switch (Anthony Roach).
168 # See http://www.scons.org/cgi-bin/wiki/GoFastButton
169 # First do: scons realclean .
170 GO_FAST_BUTTON = 1
171
172 # Add all config_vars to opts, so that they will be read and saved
173 # together with the other configure options.
174 map (lambda x: opts.AddOptions ((x,)), config_vars)
175
176 Help (usage + opts.GenerateHelpText (env))
177
178 opts.Update (env)
179
180 if GO_FAST_BUTTON:
181         env['checksums'] = 0
182         SetOption ('max_drift', 1)
183         # SetOption ('implicit_deps_unchanged', 1)
184         print "If scons feels slow, use --implicit-deps-unchanged"
185 elif env['checksums']:
186         # Always use checksums (makes more sense than timestamps).
187         SetOption ('max_drift', 0)
188         # Using *content* checksums prevents rebuilds after
189         # [re]configure if config.hh has not changed.  Too bad that it
190         # is unusably slow.
191         TargetSignatures ('content')
192
193 absbuild = Dir (env['build']).abspath
194 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
195 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
196
197
198 config_hh = os.path.join (outdir, 'config.hh')
199 version_hh = os.path.join (outdir, 'version.hh')
200
201 env.Alias ('config', config_cache)
202
203 cachedir = os.path.join (outdir, 'build-cache')
204
205 if not os.path.exists(cachedir):
206         os.makedirs(cachedir)
207
208 CacheDir (os.path.join (outdir, 'build-cache'))
209
210 # No need to set $LILYPONDPREFIX to run lily, but cannot install...
211 if env['debugging'] and not 'install' in COMMAND_LINE_TARGETS:
212         env['prefix'] = run_prefix
213
214 prefix = env['prefix']
215 bindir = os.path.join (prefix, 'bin')
216 sharedir = os.path.join (prefix, 'share')
217 libdir = os.path.join (prefix, 'lib')
218 localedir = os.path.join (sharedir, 'locale')
219 sharedir_doc_package = os.path.join (sharedir, 'doc', package.name)
220 sharedir_package = os.path.join (sharedir, package.name)
221 sharedir_package_version = os.path.join (sharedir_package, version)
222 lilypondprefix = sharedir_package_version
223
224 # junkme
225 env.Append (
226         absbuild = absbuild,
227         srcdir = srcdir,
228         )
229
230
231 def list_sort (lst):
232         sorted = lst
233         sorted.sort ()
234         return sorted
235
236
237 def configure (target, source, env):
238         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
239         def get_version (program):
240                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
241                 pipe = os.popen (command)
242                 output = pipe.read ()
243                 if pipe.close ():
244                         return None
245                 v = re.sub (vre, '\\1', output)
246                 return string.split (v, '.')
247
248         def test_program (lst, program, minimal, description, package):
249                 sys.stdout.write ('Checking %s version... ' % program)
250                 actual = get_version (program)
251                 if not actual:
252                         print 'not found'
253                         lst.append ((description, package, minimal, program,
254                                      'not installed'))
255                         return
256                 sys.stdout.write (string.join (actual, '.'))
257                 sys.stdout.write ('\n')
258                 if actual < string.split (minimal, '.'):
259                         lst.append ((description, package, minimal, program,
260                                      string.join (actual, '.')))
261
262         for i in ['bash', 'perl', 'python', 'sh']:
263                 sys.stdout.write ('Checking for %s... ' % i)
264                 c = WhereIs (i)
265                 key = string.upper (i)
266                 if c:
267                         env[key] = c
268                         sys.stdout.write (c)
269                 else:
270                         sys.stdout.write ('not found: %s (using: %s)' \
271                                           % (c, env[key]))
272                         # Hmm? abort?
273                 sys.stdout.write ('\n')
274
275         required = []
276         test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
277         test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
278         test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
279         test_program (required, 'guile-config', '1.6', 'GUILE development',
280                         'libguile-dev or guile-devel')
281         # Do not use bison 1.50 and 1.75.
282         test_program (required, 'bison', '1.25', 'Bison -- parser generator',
283                         'bison')
284         test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
285
286
287         optional = []
288         test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
289         test_program (optional, 'guile', '1.6', 'GUILE scheme',
290                         'libguile-dev or guile-devel')
291         test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
292                         'mftrace')
293         test_program (optional, 'perl', '4.0',
294                         'Perl practical efficient readonly language', 'perl')
295         #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
296
297         def CheckYYCurrentBuffer (context):
298                 context.Message ('Checking for yy_current_buffer... ')
299                 ret = conf.TryCompile ("""using namespace std;
300                 #include <FlexLexer.h>
301                 class yy_flex_lexer: public yyFlexLexer
302                 {
303                 public:
304                 yy_flex_lexer ()
305                 {
306                 yy_current_buffer = 0;
307                 }
308                 };""", '.cc')
309                 context.Result (ret)
310                 return ret
311
312         conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
313                                                 : CheckYYCurrentBuffer })
314
315         defines = {
316            'DIRSEP' : "'%s'" % os.sep,
317            'PATHSEP' : "'%s'" % os.pathsep,
318            'TOPLEVEL_VERSION' : '"' + version + '"',
319            'PACKAGE': '"' + package.name + '"',
320            'DATADIR' : '"' + sharedir + '"',
321            'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
322            'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
323            'LOCALEDIR' : '"' + localedir + '"',
324         }
325         conf.env.Append (DEFINES = defines)
326
327         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
328         PYTHON_INCLUDE = os.popen (command).read ()
329         if GO_FAST_BUTTON:
330                 env.Append (CCFLAGS = ['-I%s ' % PYTHON_INCLUDE])
331         else:
332                 env.Append (CPPPATH = PYTHON_INCLUDE)
333
334         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', '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 = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
347         for i in functions:
348                 if 0 or conf.CheckFunc (i):
349                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
350                         conf.env['DEFINES'][key] = 1
351
352         if conf.CheckYYCurrentBuffer ():
353                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
354
355         if conf.CheckLib ('dl'):
356                 pass
357
358         if conf.CheckLib ('kpathsea'):
359                 conf.env['DEFINES']['KPATHSEA'] = 1
360
361         # huh? 
362         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
363                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
364         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
365                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
366
367         if GO_FAST_BUTTON:
368                 cpppath = []
369                 if env.has_key ('CPPPATH'):
370                         cpppath = env['CPPPATH']
371         #this could happen after flower...
372         env.ParseConfig ('guile-config compile')
373
374         #this could happen only for compiling pango-*
375         if env['gui']:
376                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
377                 env.ParseConfig ('pkg-config --cflags --libs pango')
378                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
379                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
380
381                 if conf.CheckLib ('pango-1.0',
382                                   'pango_fc_font_map_add_decoder_find_func'):
383                         conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
384                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
385
386         if GO_FAST_BUTTON:
387                 # Using CCFLAGS = -I<system-dir> rather than CPPPATH = [
388                 # <system-dir>] speeds up SCons
389                 env['CCFLAGS'] += map (lambda x: '-I' + x,
390                                        env['CPPPATH'][len (cpppath):])
391                 env['CPPPATH'] = cpppath
392
393         if required:
394                 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 'realclean' in COMMAND_LINE_TARGETS:
565         command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
566         sys.stdout.write ('Running %s ... ' % command)
567         sys.stdout.write ('\n')
568         s = os.system (command)
569         if os.path.exists (config_cache):
570                 os.unlink (config_cache)
571         Exit (s)
572
573 # Declare SConscript phonies 
574 env.Alias ('minimal', config_cache)
575 env.Alias ('mf-essential', config_cache)
576
577 env.Alias ('minimal', ['lily', 'mf-essential'])
578 env.Alias ('all', ['minimal', 'mf', '.'])
579 # Do we want the doc/web separation?
580 env.Alias ('doc',
581            ['Documentation',
582             'Documentation/user',
583             'Documentation/topdocs',
584             'Documentation/bibliography',
585             'input'])
586
587 # Without target arguments, do minimal build
588 if not COMMAND_LINE_TARGETS:
589         env.Default (['minimal'])
590
591 # GNU Make rerouting compat:
592 env.Alias ('web', 'doc')
593
594
595 env.Command (version_hh, '#/VERSION',
596              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
597
598 # post-config environment update
599 env.Append (
600         run_prefix = run_prefix,
601         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
602
603         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
604         CPPPATH = [outdir, ],
605         LILYPOND_PATH = ['.', '$srcdir/input',
606                          '$srcdir/input/regression',
607                          '$srcdir/input/test',
608                          '$srcdir/input/tutorial',
609                          '$srcdir/Documentation/user',
610                          '$absbuild/mf/$out',
611 #                        os.path.join (absbuild, 'Documentation',
612 #                                      env['out']),
613 #                        os.path.join (absbuild, 'Documentation/user',
614 #                                      env['out']),
615                          ],
616         MAKEINFO_PATH = ['.', '$srcdir/Documentation/user',
617                          '$absbuild/Documentation/user/$out'],
618         )
619
620 def symlink_tree (target, source, env):
621         def mkdirs (dir):
622                 def mkdir (dir):
623                         if not dir:
624                                 os.chdir (os.sep)
625                                 return
626                         if not os.path.isdir (dir):
627                                 if os.path.exists (dir):
628                                         os.unlink (dir)
629                                 os.mkdir (dir)
630                         os.chdir (dir)
631                 map (mkdir, string.split (dir, os.sep))
632         def symlink (src, dst):
633                 os.chdir (absbuild)
634                 dir = os.path.dirname (dst)
635                 mkdirs (dir)
636                 if src[0] == '#':
637                         frm = os.path.join (srcdir, src[1:])
638                 else:
639                         depth = len (string.split (dir, '/'))
640                         if src.find ('@') > -1:
641                                 frm = os.path.join ('../' * depth,
642                                                     string.replace (src, '@',
643                                                                     env['out']))
644                         else:
645                                 frm = os.path.join ('../' * depth, src,
646                                                     env['out'])
647                 if src[-1] == '/':
648                         frm = os.path.join (frm, os.path.basename (dst))
649                 if env['verbose']:
650                         print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
651                 os.symlink (frm, os.path.basename (dst))
652         shutil.rmtree (run_prefix)
653         prefix = os.path.join (env['out'], 'usr')
654         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
655              # ^# := source dir
656              # @  := out
657              # /$ := add dst file_name
658              (('python',     'lib/lilypond/python'),
659               ('lily/',      'bin/lilypond-bin'),
660               ('scripts/',   'bin/lilypond'),
661               ('scripts/',   'bin/lilypond-book'),
662               ('mf',         'share/lilypond/dvips'),
663               ('#ps',        'share/lilypond/tex/music-drawing-routines.ps'),
664               ('mf',         'share/lilypond/afm'),
665               ('mf',         'share/lilypond/tfm'),
666               ('#mf',        'share/lilypond/fonts/mf'),
667               ('mf',         'share/lilypond/fonts/afm'),
668               ('mf',         'share/lilypond/fonts/tfm'),
669               ('mf',         'share/lilypond/fonts/type1'),
670               ('#tex',       'share/lilypond/tex/source'),
671               ('mf',         'share/lilypond/tex/generate'),
672               ('#ly',        'share/lilypond/ly'),
673               ('#scm',       'share/lilypond/scm'),
674               ('#ps',        'share/lilypond/ps'),
675               ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
676               ('elisp',      'share/lilypond/elisp')))
677         os.chdir (srcdir)
678
679 if env['debugging']:
680         stamp = os.path.join (run_prefix, 'stamp')
681         env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
682         env.Depends ('lily', stamp)
683         
684 #### dist, tar
685 def plus (a, b):
686         a + b
687
688 def cvs_entry_is_dir (line):
689         return line[0] == 'D' and line[-2] == '/'
690
691 def cvs_entry_is_file (line):
692         return line[0] == '/' and line[-2] == '/'
693
694 def cvs_dirs (dir):
695         ENTRIES = os.path.join (dir, 'CVS/Entries')
696         if not os.path.exists (ENTRIES):
697                 return []
698         entries = open (ENTRIES).readlines ()
699         dir_entries = filter (cvs_entry_is_dir, entries)
700         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
701                     dir_entries)
702         return dirs + map (cvs_dirs, dirs)
703
704 def cvs_files (dir):
705         ENTRIES = os.path.join (dir, 'CVS/Entries')
706         entries = open (ENTRIES).readlines ()
707         file_entries = filter (cvs_entry_is_file, entries)
708         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
709         return map (lambda x: os.path.join (dir, x), files)
710
711 def flatten (tree, lst):
712         if type (tree) == type ([]):
713                 for i in tree:
714                         if type (i) == type ([]):
715                                 flatten (i, lst)
716                         else:
717                                 lst.append (i)
718         return lst
719
720 subdirs = flatten (cvs_dirs ('.'), [])
721 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
722 foo = map (lambda x: env.TXT (x + '.txt',
723                               os.path.join ('Documentation/topdocs', x)),
724            readme_files)
725 txt_files = map (lambda x: x + '.txt', readme_files)
726 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
727 tar_base = package.name + '-' + version
728 tar_name = tar_base + '.tar.gz'
729 ball_prefix = os.path.join (outdir, tar_base)
730 tar_ball = os.path.join (outdir, tar_name)
731
732 dist_files = src_files + txt_files
733 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
734 map (lambda x: env.Depends (tar_ball, x), ball_files)
735 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
736                             'ln $SOURCE $TARGET'), dist_files)
737 tar = env.Command (tar_ball, src_files,
738                    ['rm -f $$(find $TARGET.dir -name .sconsign)',
739                     'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
740 env.Alias ('tar', tar)
741
742 dist_ball = os.path.join (package.release_dir, tar_name)
743 env.Command (dist_ball, tar_ball,
744              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
745              + 'ln $SOURCE $TARGET')
746 env.Depends ('dist', dist_ball)
747 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
748 patch = env.PATCH (patch_name, tar_ball)
749 env.Depends (patch_name, dist_ball)
750 env.Alias ('release', patch)
751
752 #### web
753 web_base = os.path.join (outdir, 'web')
754 web_ball = web_base + '.tar.gz'
755 env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
756 web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
757 web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"')
758 env['web_path'] = web_path
759 web_list = os.path.join (outdir, 'weblist')
760 # compatible make heritits
761 # fixme: generate in $outdir is cwd/builddir
762 env.Command (web_list,
763              ## this is correct, but takes > 5min if you have a peder :-)
764              ##'doc',
765              '#/VERSION',
766              ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
767               'cd $absbuild && $footify $$(find . -name "*.html" -print)',
768               'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
769               'cd $absbuild && find Documentation input $web_path \
770               > $TARGET',
771               '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
772               '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
773               'cd $absbuild && ls *.html >> $TARGET',])
774 env.Command (web_ball, web_list,
775              ['cat $SOURCE | tar -C $absbuild -czf $TARGET -T -',])
776 #env.Alias ('web', web_ball)
777 www_base = os.path.join (outdir, 'www')
778 www_ball = www_base + '.tar.gz'
779 env.Command (www_ball, web_ball,
780              ['rm -rf $out/tmp',
781               'mkdir -p $absbuild/$out/tmp',
782               'tar -C $absbuild/$out/tmp -xzf $SOURCE',
783               'cd $absbuild/$out/tmp && for i in $$(find . -name "$out"); do mv $$i $$(dirname $$i)/out-www; done',
784               'tar -C $absbuild/$out/tmp -czf $TARGET .'])
785 env.Alias ('web', www_ball)
786
787 #### tags
788 env.Append (
789         ETAGSFLAGS = ["""--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\1/'""",
790                       """--regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\1/'"""])
791 # filter-out some files?
792 env.Command ('TAGS', src_files, 'etags $ETAGSFLAGS $SOURCES')
793
794
795 # Note: SConscripts are only needed in directories where something needs
796 # to be done, building or installing
797 for d in subdirs:
798         if os.path.exists (os.path.join (d, 'SConscript')):
799                 b = os.path.join (env['build'], d, env['out'])
800                 # Support clean sourcetree build (--srcdir build)
801                 # and ./out build.
802                 if os.path.abspath (b) != os.path.abspath (d):
803                         env.BuildDir (b, d, duplicate = 0)
804                 SConscript (os.path.join (b, 'SConscript'))
805