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