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