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