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