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