]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
* stepmake/aclocal.m4 (STEPMAKE_PANGO_FT2): Define HAVE_PANGO16 too.
[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 = '(pkg-config --modversion %(program)s || %(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                 if v[-1] == '\n':
247                         v = v[:-1]
248                 return string.split (v, '.')
249
250         def test_program (lst, program, minimal, description, package):
251                 sys.stdout.write ('Checking %s version... ' % program)
252                 actual = get_version (program)
253                 if not actual:
254                         print 'not found'
255                         lst.append ((description, package, minimal, program,
256                                      'not installed'))
257                         return 0
258                 sys.stdout.write (string.join (actual, '.'))
259                 sys.stdout.write ('\n')
260                 if actual < string.split (minimal, '.'):
261                         lst.append ((description, package, minimal, program,
262                                      string.join (actual, '.')))
263                         return 0
264                 return 1
265
266         def test_lib (lst, program, minimal, description):
267                 # FIXME: test for Debian or RPM (or -foo?) based dists
268                 # to guess (or get correct!: apt-cache search?)
269                 # package name.
270                 #if os.system ('pkg-config --atleast-version=0 freetype2'):
271                 # barf
272                 if test_program (lst, program, minimal, description,
273                                  'lib%(program)s-dev or %(program)s-devel'
274                                  % vars ()):
275                         env.ParseConfig ('pkg-config --cflags --libs %(program)s'
276                                          % vars ())
277                         return 1
278                 return 0
279
280         for i in ['bash', 'perl', 'python', 'sh']:
281                 sys.stdout.write ('Checking for %s... ' % i)
282                 c = WhereIs (i)
283                 key = string.upper (i)
284                 if c:
285                         env[key] = c
286                         sys.stdout.write (c)
287                 else:
288                         sys.stdout.write ('not found: %s (using: %s)' \
289                                           % (c, env[key]))
290                         # Hmm? abort?
291                 sys.stdout.write ('\n')
292
293         required = []
294         test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
295         test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
296         test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
297         test_program (required, 'guile-config', '1.6', 'GUILE development',
298                         'libguile-dev or guile-devel')
299         # Do not use bison 1.50 and 1.75.
300         test_program (required, 'bison', '1.25', 'Bison -- parser generator',
301                         'bison')
302         test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
303
304         optional = []
305         test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
306         test_program (optional, 'guile', '1.6', 'GUILE scheme', 'guile')
307         test_program (optional, 'mftrace', '1.1.0', 'Metafont tracing Type1',
308                         'mftrace')
309         test_program (optional, 'perl', '4.0',
310                         'Perl practical efficient readonly language', 'perl')
311         #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
312
313         def CheckYYCurrentBuffer (context):
314                 context.Message ('Checking for yy_current_buffer... ')
315                 ret = conf.TryCompile ("""using namespace std;
316                 #include <FlexLexer.h>
317                 class yy_flex_lexer: public yyFlexLexer
318                 {
319                 public:
320                 yy_flex_lexer ()
321                 {
322                 yy_current_buffer = 0;
323                 }
324                 };""", '.cc')
325                 context.Result (ret)
326                 return ret
327
328         conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
329                                                 : CheckYYCurrentBuffer })
330
331         defines = {
332            'DIRSEP' : "'%s'" % os.sep,
333            'PATHSEP' : "'%s'" % os.pathsep,
334            'PACKAGE': '"%s"' % package.name,
335            'DATADIR' : '"%s"' % sharedir,
336            'PACKAGE_DATADIR' : '"%s"' % sharedir_package,
337            'LOCALEDIR' : '"%s"' %localedir,
338         }
339         conf.env.Append (DEFINES = defines)
340
341         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
342         PYTHON_INCLUDE = os.popen (command).read ()
343         if env['fast']:
344                 env.Append (CCFLAGS = ['-I%s ' % PYTHON_INCLUDE])
345         else:
346                 env.Append (CPPPATH = PYTHON_INCLUDE)
347
348         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'libio.h',
349                    'Python.h')
350         for i in headers:
351                 if conf.CheckCHeader (i):
352                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
353                         conf.env['DEFINES'][key] = 1
354
355         ccheaders = ('sstream',)
356         for i in ccheaders:
357                 if conf.CheckCXXHeader (i):
358                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
359                         conf.env['DEFINES'][key] = 1
360
361         functions = ('fopencookie', 'funopen',
362                      'gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
363         for i in functions:
364                 if 0 or conf.CheckFunc (i):
365                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
366                         conf.env['DEFINES'][key] = 1
367
368         if conf.CheckYYCurrentBuffer ():
369                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
370
371         if conf.CheckLib ('dl'):
372                 pass
373
374         if conf.CheckLib ('kpathsea'):
375                 conf.env['DEFINES']['KPATHSEA'] = 1
376
377         # huh? 
378         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
379                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
380         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
381                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
382
383         # FIXME fc3 - move to kpath-guile/SConscript?
384         conf.env['DEFINES']['HAVE_LIBKPATHSEA_SO'] = '1'
385
386         if env['fast']:
387                 cpppath = []
388                 if env.has_key ('CPPPATH'):
389                         cpppath = env['CPPPATH']
390
391         ## FIXME: linkage, check for libguile.h and scm_boot_guile
392         #this could happen after flower...
393         env.ParseConfig ('guile-config compile')
394
395         if test_lib (required, 'freetype2', '0.0',
396                      'Development files for FreeType 2 font engine'):
397                 conf.env['DEFINES']['HAVE_FREETYPE2'] = '1'
398                 
399         if test_lib (required, 'pangoft2', '1.6.0',
400                      'Development files for pango, with fontconfig2'):
401                 conf.env['DEFINES']['HAVE_PANGO_FT2'] = '1'
402                 conf.env['DEFINES']['HAVE_PANGO16'] = '1'
403
404         if test_lib (optional, 'fontconfig', '2.2.0',
405                      'Development files for fontconfig'):
406                 conf.env['DEFINES']['HAVE_FONTCONFIG'] = '1'
407         
408         #this could happen only for compiling pango-*
409         if env['gui']:
410                 test_lib (required, 'gtk+-2.0', '2.4.0',
411                           'Development files for GTK+')
412                 if test_lib (required, 'pango', '1.6.0',
413                           'Development files for pango'):
414                         conf.env['DEFINES']['HAVE_PANGO16'] = '1'
415                         
416                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
417                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
418                 if conf.CheckLib ('pango-1.0',
419                                   'pango_fc_font_map_add_decoder_find_func'):
420                         conf.env['DEFINES']['HAVE_PANGO16'] = '1'
421                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
422
423         if env['fast']:
424                 # Using CCFLAGS = -I<system-dir> rather than CPPPATH = [
425                 # <system-dir>] speeds up SCons
426                 env['CCFLAGS'] += map (lambda x: '-I' + x,
427                                        env['CPPPATH'][len (cpppath):])
428                 env['CPPPATH'] = cpppath
429
430         if required:
431                 print
432                 print '********************************'
433                 print 'Please install required packages'
434                 for i in required:
435                         print '%s:      %s-%s or newer (found: %s %s)' % i
436                 Exit (1)
437
438         if optional:
439                 print
440                 print '*************************************'
441                 print 'Consider installing optional packages'
442                 for i in optional:
443                         print '%s:      %s-%s or newer (found: %s %s)' % i
444
445         return conf.Finish ()
446
447 def config_header (target, source, env):
448         config = open (str (target[0]), 'w')
449         for i in list_sort (env['DEFINES'].keys ()):
450                 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
451         config.close ()
452 env.Command (config_hh, config_cache, config_header)
453
454 # hmm?
455 def xuniquify (lst):
456         n = []
457         for i in lst:
458                 if not i in n:
459                         n.append (i)
460         lst = n
461         return lst
462
463 def uniquify (lst):
464         d = {}
465         n = len (lst)
466         i = 0
467         while i < n:
468                 if not d.has_key (lst[i]):
469                         d[lst[i]] = 1
470                         i += 1
471                 else:
472                         del lst[i]
473                         n -= 1
474         return lst
475
476 def uniquify_config_vars (env):
477         for i in config_vars:
478                 if env.has_key (i) and type (env[i]) == type ([]):
479                         env[i] = uniquify (env[i])
480
481 def save_config_cache (env):
482         ## FIXME: Is this smart, using option cache for saving
483         ## config.cache?  I cannot seem to find the official method.
484         uniquify_config_vars (env)
485         opts.Save (config_cache, env)
486
487         if 'config' in COMMAND_LINE_TARGETS:
488                 sys.stdout.write ('\n')
489                 sys.stdout.write ('LilyPond configured')
490                 sys.stdout.write ('\n')
491                 sys.stdout.write ('now run')
492                 sys.stdout.write ('\n')
493                 sys.stdout.write ('    scons [TARGET|DIR]...')
494                 sys.stdout.write ('\n')
495                 Exit (0)
496         elif not env['checksums']:
497                 # When using timestams, config.hh is NEW.  The next
498                 # build triggers recompilation of everything.  Exiting
499                 # here makes SCons use the actual timestamp for config.hh
500                 # and prevents recompiling everything the next run.
501                 command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS)
502                 sys.stdout.write ('Running %s ... ' % command)
503                 sys.stdout.write ('\n')
504                 s = os.system (command)
505                 Exit (s)
506
507
508 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
509         os.unlink (config_cache)
510 # WTF?
511 # scons: *** Calling Configure from Builders is not supported.
512 # env.Command (config_cache, None, configure)
513 if not os.path.exists (config_cache) \
514    or (os.stat ('SConstruct')[stat.ST_MTIME]
515        > os.stat (config_cache)[stat.ST_MTIME]):
516         env = configure (None, None, env)
517         save_config_cache (env)
518 elif env['checksums']:
519         # just save everything
520         save_config_cache (env)
521
522 #urg how does #/ subst work?
523 Export ('env')
524 SConscript ('buildscripts/builder.py')
525
526 env.PrependENVPath ('PATH',
527                     os.path.join (env['absbuild'], env['out'], 'usr/bin'))
528
529 if os.environ.has_key ('TEXMF'):
530         env.Append (ENV = {'TEXMF' : os.environ['TEXMF']})
531 env.Append (ENV = {
532         'TEXMF' : '{$LILYPONDPREFIX,' \
533         + os.popen ('kpsexpand \$TEXMF').read ()[:-1] + '}',
534         'LILYPONDPREFIX' : os.path.join (run_prefix, 'share/lilypond'),
535         })
536
537 BUILD_ABC2LY = '${set__x}$PYTHON $srcdir/scripts/abc2ly.py'
538 BUILD_LILYPOND = '${set__x}$PYTHON $srcdir/scripts/lilypond.py${__verbose}'
539 BUILD_LILYPOND_BIN = '$absbuild/$out/lilypond-bin ${__verbose}'
540 BUILD_LILYPOND_BOOK = '$PYTHON $srcdir/scripts/lilypond-book.py --verbose'
541
542
543 # post-option environment-update
544 env.Append (
545         bindir = bindir,
546         sharedir = sharedir,
547         lilypond_datadir = sharedir_package,
548         localedir = localedir,
549         local_lilypond_datadir = sharedir_package_version,
550         lilypondprefix = lilypondprefix,
551         sharedir_package = sharedir_package,
552         sharedir_doc_package = sharedir_doc_package,
553         sharedir_package_version = sharedir_package_version,
554
555         LILYPOND = BUILD_LILYPOND,
556         ABC2LY = BUILD_ABC2LY,
557         LILYPOND_BOOK = BUILD_LILYPOND_BOOK,
558         LILYPOND_BOOK_FORMAT = 'texi-html',
559         MAKEINFO_FLAGS = '--css-include=$srcdir/Documentation/texinfo.css',
560
561         TEXI2DVI_PAPERSIZE = '@afourpaper',
562         TEXI2DVI_FLAGS = [ '-t $TEXI2DVI_PAPERSIZE'],
563         DVIPS_PAPERSIZE = 'a4',
564         DVIPS_FLAGS = ['-t $DVIPS_PAPERSIZE',
565                        '-u+lilypond.map',
566                        '-u+ec-mftrace.map'],
567         PSPDF_FLAGS = ['-sPAPERSIZE=$DVIPS_PAPERSIZE'],
568         )
569
570 if env['debugging']:
571         env.Append (CCFLAGS = ['-g', '-pipe'])
572 if env['optimising']:
573         env.Append (CCFLAGS = '-O2')
574         env.Append (CXXFLAGS = ['-DSTRING_UTILS_INLINED'])
575 if env['warnings']:
576         env.Append (CCFLAGS = ['-W', '-Wall'])
577         env.Append (CXXFLAGS = ['-Wconversion'])
578
579 # ugr,huh?
580 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
581
582 if env['verbose']:
583         env['__verbose'] = ' --verbose'
584         env['set__x'] = 'set -x;'
585
586
587 ## Explicit target and dependencies
588
589 if 'clean' in COMMAND_LINE_TARGETS:
590         # ugh: prevent reconfigure instead of clean
591         os.system ('touch %s' % config_cache)
592         
593         command = sys.argv[0] + ' -c .'
594         sys.stdout.write ('Running %s ... ' % command)
595         sys.stdout.write ('\n')
596         s = os.system (command)
597         if os.path.exists (config_cache):
598                 os.unlink (config_cache)
599         Exit (s)
600
601 if 'sconsclean' in COMMAND_LINE_TARGETS:
602         command = 'rm -rf scons.cache $(find . -name ".scon*")'
603         s = os.system (command)
604         if os.path.exists (config_cache):
605                 os.unlink (config_cache)
606         Exit (s)
607         
608 if 'realclean' in COMMAND_LINE_TARGETS:
609         command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
610         sys.stdout.write ('Running %s ... ' % command)
611         sys.stdout.write ('\n')
612         s = os.system (command)
613         if os.path.exists (config_cache):
614                 os.unlink (config_cache)
615         Exit (s)
616
617 # Declare SConscript phonies 
618 env.Alias ('minimal', config_cache)
619 env.Alias ('mf-essential', config_cache)
620
621 env.Alias ('minimal', ['lily', 'mf-essential'])
622 env.Alias ('all', ['minimal', 'mf', '.'])
623 # Do we want the doc/web separation?
624 env.Alias ('doc',
625            ['Documentation',
626             'Documentation/user',
627             'Documentation/topdocs',
628             'Documentation/bibliography',
629             'input'])
630
631 # Without target arguments, do minimal build
632 if not COMMAND_LINE_TARGETS:
633         env.Default (['minimal'])
634
635 # GNU Make rerouting compat:
636 env.Alias ('web', 'doc')
637
638
639 env.Command (version_hh, '#/VERSION',
640              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
641
642 # post-config environment update
643 env.Append (
644         run_prefix = run_prefix,
645         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
646
647         # FIXME: move to lily/SConscript?
648         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),
649                    os.path.join (absbuild, 'kpath-guile', env['out']),
650                    os.path.join (absbuild, 'ttftool', env['out']),],
651         CPPPATH = [outdir, ],
652         LILYPOND_PATH = ['.', '$srcdir/input',
653                          '$srcdir/input/regression',
654                          '$srcdir/input/test',
655                          '$srcdir/input/tutorial',
656                          '$srcdir/Documentation/user',
657                          '$absbuild/mf/$out',
658 #                        os.path.join (absbuild, 'Documentation',
659 #                                      env['out']),
660 #                        os.path.join (absbuild, 'Documentation/user',
661 #                                      env['out']),
662                          ],
663         MAKEINFO_PATH = ['.', '$srcdir/Documentation/user',
664                          '$absbuild/Documentation/user/$out'],
665         )
666
667 def symlink_tree (target, source, env):
668         def mkdirs (dir):
669                 def mkdir (dir):
670                         if not dir:
671                                 os.chdir (os.sep)
672                                 return
673                         if not os.path.isdir (dir):
674                                 if os.path.exists (dir):
675                                         os.unlink (dir)
676                                 os.mkdir (dir)
677                         os.chdir (dir)
678                 map (mkdir, string.split (dir, os.sep))
679         def symlink (src, dst):
680                 os.chdir (absbuild)
681                 dir = os.path.dirname (dst)
682                 mkdirs (dir)
683                 if src[0] == '#':
684                         frm = os.path.join (srcdir, src[1:])
685                 else:
686                         depth = len (string.split (dir, '/'))
687                         if src.find ('@') > -1:
688                                 frm = os.path.join ('../' * depth,
689                                                     string.replace (src, '@',
690                                                                     env['out']))
691                         else:
692                                 frm = os.path.join ('../' * depth, src,
693                                                     env['out'])
694                 if src[-1] == '/':
695                         frm = os.path.join (frm, os.path.basename (dst))
696                 if env['verbose']:
697                         print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
698                 os.symlink (frm, os.path.basename (dst))
699         shutil.rmtree (run_prefix)
700         prefix = os.path.join (env['out'], 'usr')
701         map (lambda x: symlink (x[0], os.path.join (prefix,
702                                                     x[1] % {'ver' : version})),
703              # ^# := source dir
704              # @  := out
705              # /$ := add dst file_name
706              (('python',     'lib/lilypond/python'),
707               ('lily/',      'bin/lilypond'),
708               ('scripts/',   'bin/convert-ly'),
709               ('scripts/',   'bin/lilypond-book'),
710               ('scripts/',   'bin/ps2png'),
711               ('mf',         'share/lilypond/%(ver)s/dvips/mf-out'),
712               ('#ps',        'share/lilypond/%(ver)s/dvips/ps'),
713               ('#ps',        'share/lilypond/%(ver)s/tex/music-drawing-routines.ps'),
714               ('mf',         'share/lilypond/%(ver)s/otf'),
715               ('mf',         'share/lilypond/%(ver)s/tfm'),
716               ('tex',        'share/lilypond/%(ver)s/tex/enc'),
717               ('#mf',        'share/lilypond/%(ver)s/fonts/mf'),
718               ('mf',         'share/lilypond/%(ver)s/fonts/map'),
719               ('mf',         'share/lilypond/%(ver)s/fonts/otf'),
720               ('mf',         'share/lilypond/%(ver)s/fonts/tfm'),
721               ('mf',         'share/lilypond/%(ver)s/fonts/type1'),
722               ('#tex',       'share/lilypond/%(ver)s/tex/source'),
723               ('tex',        'share/lilypond/%(ver)s/tex/tex-out'),
724               ('mf',         'share/lilypond/%(ver)s/tex/mf-out'),
725               ('#ly',        'share/lilypond/%(ver)s/ly'),
726               ('#scm',       'share/lilypond/%(ver)s/scm'),
727               ('#scripts',   'share/lilypond/%(ver)s/scripts'),
728               ('#ps',        'share/lilypond/%(ver)s/ps'),
729               ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
730               ('elisp',      'share/lilypond/%(ver)s/elisp')))
731         os.chdir (srcdir)
732
733 if env['debugging']:
734         stamp = os.path.join (run_prefix, 'stamp')
735         env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
736         env.Depends ('lily', stamp)
737         
738 #### dist, tar
739 def plus (a, b):
740         a + b
741
742 def cvs_entry_is_dir (line):
743         return line[0] == 'D' and line[-2] == '/'
744
745 def cvs_entry_is_file (line):
746         return line[0] == '/' and line[-2] == '/'
747
748 def cvs_dirs (dir):
749         ENTRIES = os.path.join (dir, 'CVS/Entries')
750         if not os.path.exists (ENTRIES):
751                 return []
752         entries = open (ENTRIES).readlines ()
753         dir_entries = filter (cvs_entry_is_dir, entries)
754         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
755                     dir_entries)
756         return dirs + map (cvs_dirs, dirs)
757
758 def cvs_files (dir):
759         ENTRIES = os.path.join (dir, 'CVS/Entries')
760         entries = open (ENTRIES).readlines ()
761         file_entries = filter (cvs_entry_is_file, entries)
762         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
763         return map (lambda x: os.path.join (dir, x), files)
764
765 def flatten (tree, lst):
766         if type (tree) == type ([]):
767                 for i in tree:
768                         if type (i) == type ([]):
769                                 flatten (i, lst)
770                         else:
771                                 lst.append (i)
772         return lst
773
774 if env['fast']\
775    and 'all' not in COMMAND_LINE_TARGETS\
776    and 'doc' not in COMMAND_LINE_TARGETS\
777    and 'web' not in COMMAND_LINE_TARGETS\
778    and 'install' not in COMMAND_LINE_TARGETS\
779    and 'clean' not in COMMAND_LINE_TARGETS:
780         subdirs = ['lily', 'lily/include',
781                    'flower', 'flower/include',
782                    'kpath-guile',
783                    'ttftool',
784                    'mf',
785                    ]
786 else:
787         subdirs = flatten (cvs_dirs ('.'), [])
788
789 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
790 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
791 txt_files = map (lambda x: x + '.txt', readme_files)
792
793
794 #
795 # speeds up build by +- 5% 
796
797 if not env['fast']:
798         foo = map (lambda x: env.TXT (x + '.txt',
799                                       os.path.join ('Documentation/topdocs', x)),
800                    readme_files)
801         tar_base = package.name + '-' + version
802         tar_name = tar_base + '.tar.gz'
803         ball_prefix = os.path.join (outdir, tar_base)
804         tar_ball = os.path.join (outdir, tar_name)
805
806         dist_files = src_files + txt_files
807         ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
808         map (lambda x: env.Depends (tar_ball, x), ball_files)
809         map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
810                                     'ln $SOURCE $TARGET'), dist_files)
811         tar = env.Command (tar_ball, src_files,
812                            ['rm -f $$(find $TARGET.dir -name .sconsign)',
813                             'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
814         env.Alias ('tar', tar)
815
816         dist_ball = os.path.join (package.release_dir, tar_name)
817         env.Command (dist_ball, tar_ball,
818                      'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
819                      + 'ln $SOURCE $TARGET')
820         env.Depends ('dist', dist_ball)
821         patch_name = os.path.join (outdir, tar_base + '.diff.gz')
822         patch = env.PATCH (patch_name, tar_ball)
823         env.Depends (patch_name, dist_ball)
824         env.Alias ('release', patch)
825
826 #### web
827 if not env['fast']:
828         web_base = os.path.join (outdir, 'web')
829         web_ball = web_base + '.tar.gz'
830         env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
831         web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
832         web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"')
833         env['web_path'] = web_path
834         web_list = os.path.join (outdir, 'weblist')
835         # compatible make heritits
836         # fixme: generate in $outdir is cwd/builddir
837         env.Command (web_list,
838                      ## this is correct, but takes > 5min if you have a peder :-)
839                      ##'doc',
840                      '#/VERSION',
841                      ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
842                       'cd $absbuild && $footify $$(find . -name "*.html" -print)',
843                       'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
844                       'cd $absbuild && find Documentation input $web_path \
845                       > $TARGET',
846                       '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
847                       '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
848                       'cd $absbuild && ls *.html >> $TARGET',])
849         env.Command (web_ball, web_list,
850                      ['cat $SOURCE | tar -C $absbuild -czf $TARGET -T -',])
851         #env.Alias ('web', web_ball)
852         www_base = os.path.join (outdir, 'www')
853         www_ball = www_base + '.tar.gz'
854         env.Command (www_ball, web_ball,
855                      ['rm -rf $out/tmp',
856                       'mkdir -p $absbuild/$out/tmp',
857                       'tar -C $absbuild/$out/tmp -xzf $SOURCE',
858                       'cd $absbuild/$out/tmp && for i in $$(find . -name "$out"); '
859                       + ' do mv $$i $$(dirname $$i)/out-www; done',
860                       'tar -C $absbuild/$out/tmp -czf $TARGET .'])
861         env.Alias ('web', www_ball)
862
863 #### tags
864 env.Append (
865         ETAGSFLAGS = """--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\\1/' \
866         --regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\\1/'""")
867 code_ext = ['.cc', '.hh', '.scm', '.tcc',]
868 env.Command ('TAGS', filter (lambda x: os.path.splitext (x)[1] in code_ext,
869                              src_files),
870              'etags $ETAGSFLAGS $SOURCES')
871
872 # Note: SConscripts are only needed in directories where something needs
873 # to be done, building or installing
874 for d in subdirs:
875         if os.path.exists (os.path.join (d, 'SConscript')):
876                 b = os.path.join (env['build'], d, env['out'])
877                 # Support clean sourcetree build (--srcdir build)
878                 # and ./out build.
879                 if os.path.abspath (b) != os.path.abspath (d):
880                         env.BuildDir (b, d, duplicate = 0)
881                 SConscript (os.path.join (b, 'SConscript'))
882