]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
* scm/lily.scm: Remove horrendous running-from-gui? kludge.
[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 lidbir_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
662         # global build verbosity switch
663         __verbose = ' --verbose',
664         
665         LILYPOND = BUILD_LILYPOND,
666         ABC2LY = BUILD_ABC2LY,
667         LILYPOND_BOOK = BUILD_LILYPOND_BOOK,
668         LILYPOND_BOOK_FORMAT = 'texi-html',
669         MAKEINFO_FLAGS = '--css-include=$srcdir/Documentation/texinfo.css',
670         # should not be necessary
671         # PYTHONPATH = ['$absbuild/python/$out'],
672         TEXI2DVI_PAPERSIZE = '@afourpaper',
673         TEXI2DVI_FLAGS = [ '-t$TEXI2DVI_PAPERSIZE'],
674         DVIPS_PAPERSIZE = 'a4',
675         DVIPS_FLAGS = ['-t$DVIPS_PAPERSIZE',
676                        '-u+lilypond.map',
677                        '-u+ec-mftrace.map'],
678         PSPDF_FLAGS = ['-sPAPERSIZE=$DVIPS_PAPERSIZE'],
679         )
680
681 env.Append (CCFLAGS = ['-pipe', '-Wno-pmf-conversions'])
682 if env['debugging']:
683         env.Append (CCFLAGS = ['-g'])
684 if env['optimising']:
685         env.Append (CCFLAGS = '-O2')
686         env.Append (CXXFLAGS = ['-DSTRING_UTILS_INLINED'])
687 if env['warnings']:
688         env.Append (CCFLAGS = ['-W', '-Wall'])
689         env.Append (CXXFLAGS = ['-Wconversion'])
690
691 # ugr,huh?
692 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
693 # FIXME: ParseConfig ignores -L flag?
694 env.Append (LINKFLAGS = ['-L/usr/X11R6/lib'])
695
696 if env['verbose']:
697         env['__verbose'] = ' --verbose'
698         env['set__x'] = 'set -x;'
699
700
701 ## Explicit target and dependencies
702
703 if 'clean' in COMMAND_LINE_TARGETS:
704         # ugh: prevent reconfigure instead of clean
705         os.system ('touch %s' % config_cache)
706         
707         command = sys.argv[0] + ' -c .'
708         sys.stdout.write ('Running %s ... ' % command)
709         sys.stdout.write ('\n')
710         s = os.system (command)
711         if os.path.exists (config_cache):
712                 os.unlink (config_cache)
713         Exit (s)
714
715 if 'sconsclean' in COMMAND_LINE_TARGETS:
716         command = 'rm -rf scons.cache $(find . -name ".scon*")'
717         s = os.system (command)
718         if os.path.exists (config_cache):
719                 os.unlink (config_cache)
720         Exit (s)
721         
722 if 'realclean' in COMMAND_LINE_TARGETS:
723         command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
724         sys.stdout.write ('Running %s ... ' % command)
725         sys.stdout.write ('\n')
726         s = os.system (command)
727         if os.path.exists (config_cache):
728                 os.unlink (config_cache)
729         Exit (s)
730
731 # Declare SConscript phonies 
732 env.Alias ('minimal', config_cache)
733 env.Alias ('mf-essential', config_cache)
734
735 env.Alias ('minimal', ['lily', 'mf-essential'])
736 env.Alias ('all', ['minimal', 'mf', '.'])
737 # Do we want the doc/web separation?
738 env.Alias ('doc',
739            ['Documentation',
740             'Documentation/user',
741             'Documentation/topdocs',
742             'Documentation/bibliography',
743             'input'])
744
745 # Without target arguments, do minimal build
746 if not COMMAND_LINE_TARGETS:
747         env.Default (['minimal'])
748
749 # GNU Make rerouting compat:
750 env.Alias ('web', 'doc')
751
752
753 env.Command (version_hh, '#/VERSION',
754              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
755
756 # post-config environment update
757 env.Append (
758         run_prefix = run_prefix,
759         LILYPONDPREFIX = LILYPONDPREFIX,
760
761         # FIXME: move to lily/SConscript?
762         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),
763                    os.path.join (absbuild, 'kpath-guile', env['out']),],
764         CPPPATH = [outdir, ],
765         LILYPOND_PATH = ['.',
766                          '$srcdir/input',
767                          '$srcdir/input/regression',
768                          '$srcdir/input/test',
769                          '$srcdir/input/tutorial',
770                          '$srcdir/Documentation/user',
771                          '$absbuild/mf/$out',
772 #                        os.path.join (absbuild, 'Documentation',
773 #                                      env['out']),
774 #                        os.path.join (absbuild, 'Documentation/user',
775 #                                      env['out']),
776                          ],
777         MAKEINFO_PATH = ['.', '$srcdir/Documentation/user',
778                          '$absbuild/Documentation/user/$out'],
779         )
780
781 def symlink_tree (target, source, env):
782         def mkdirs (dir):
783                 def mkdir (dir):
784                         if not dir:
785                                 os.chdir (os.sep)
786                                 return
787                         if not os.path.isdir (dir):
788                                 if os.path.exists (dir):
789                                         os.unlink (dir)
790                                 os.mkdir (dir)
791                         os.chdir (dir)
792                 map (mkdir, string.split (dir, os.sep))
793         def symlink (src, dst):
794                 os.chdir (absbuild)
795                 dir = os.path.dirname (dst)
796                 mkdirs (dir)
797                 if src[0] == '#':
798                         frm = os.path.join (srcdir, src[1:])
799                 else:
800                         depth = len (string.split (dir, '/'))
801                         if src.find ('@') > -1:
802                                 frm = os.path.join ('../' * depth,
803                                                     string.replace (src, '@',
804                                                                     env['out']))
805                         else:
806                                 frm = os.path.join ('../' * depth, src,
807                                                     env['out'])
808                 if src[-1] == '/':
809                         frm = os.path.join (frm, os.path.basename (dst))
810                 if env['verbose']:
811                         print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
812                 os.symlink (frm, os.path.basename (dst))
813         shutil.rmtree (run_prefix)
814         prefix = os.path.join (env['out'], 'usr')
815         map (lambda x: symlink (x[0], os.path.join (prefix,
816                                                     x[1] % {'ver' : version})),
817              # ^# := source dir
818              # @  := out
819              # /$ := add dst file_name
820              (('python',     'lib/lilypond/python'),
821               # ugh
822               ('python',     'share/lilypond/%(ver)s/python'),
823               ('lily/',      'bin/lilypond'),
824               ('scripts/',   'bin/convert-ly'),
825               ('scripts/',   'bin/lilypond-book'),
826               ('scripts/',   'bin/ps2png'),
827               ('mf',         'share/lilypond/%(ver)s/dvips/mf-out'),
828               ('#ps',        'share/lilypond/%(ver)s/dvips/ps'),
829               ('#ps',        'share/lilypond/%(ver)s/tex/music-drawing-routines.ps'),
830               ('mf',         'share/lilypond/%(ver)s/otf'),
831               ('mf',         'share/lilypond/%(ver)s/tfm'),
832               ('tex',        'share/lilypond/%(ver)s/tex/enc'),
833               ('#mf',        'share/lilypond/%(ver)s/fonts/mf'),
834               ('mf',         'share/lilypond/%(ver)s/fonts/map'),
835               ('mf',         'share/lilypond/%(ver)s/fonts/otf'),
836               ('mf',         'share/lilypond/%(ver)s/fonts/tfm'),
837               ('mf',         'share/lilypond/%(ver)s/fonts/type1'),
838               ('#tex',       'share/lilypond/%(ver)s/tex/source'),
839               ('tex',        'share/lilypond/%(ver)s/tex/tex-out'),
840               ('mf',         'share/lilypond/%(ver)s/tex/mf-out'),
841               ('#ly',        'share/lilypond/%(ver)s/ly'),
842               ('#scm',       'share/lilypond/%(ver)s/scm'),
843               ('#scripts',   'share/lilypond/%(ver)s/scripts'),
844               ('#ps',        'share/lilypond/%(ver)s/ps'),
845               ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
846               ('elisp',      'share/lilypond/%(ver)s/elisp')))
847
848         print "FIXME: BARF BARF BARF"
849         os.chdir (absbuild)
850         out = env['out']
851         ver = version
852         prefix = os.path.join (env['out'], 'usr/share/lilypond/%(ver)s/fonts'
853                                % vars ())
854         for ext in ('enc', 'map', 'otf', 'svg', 'tfm', 'pfa'):
855                 dir = os.path.join (absbuild, prefix, ext)
856                 os.system ('rm -f ' + dir)
857                 mkdirs (dir)
858                 os.chdir (dir)
859                 os.system ('ln -s ../../../../../../../mf/%(out)s/*.%(ext)s .'
860                            % vars ())
861         os.chdir (srcdir)
862
863 if 1: #env['debugging']:
864         stamp = os.path.join (run_prefix, 'stamp')
865         env.Command (stamp, ['#/SConstruct', '#/VERSION'],
866                      [symlink_tree, 'touch $TARGET'])
867         env.Depends ('lily', stamp)
868         
869 #### dist, tar
870 def plus (a, b):
871         a + b
872
873 def cvs_entry_is_dir (line):
874         return line[0] == 'D' and line[-2] == '/'
875
876 def cvs_entry_is_file (line):
877         return line[0] == '/' and line[-2] == '/'
878
879 def cvs_dirs (dir):
880         entries = os.path.join (dir, 'CVS/Entries')
881         if not os.path.exists (entries):
882                 return []
883         entries = open (entries).readlines ()
884         dir_entries = filter (cvs_entry_is_dir, entries)
885         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
886                     dir_entries)
887         return dirs + map (cvs_dirs, dirs)
888
889 def cvs_files (dir):
890         entries = os.path.join (dir, 'CVS/Entries')
891         if not os.path.exists (entries):
892                 return []
893         entries = open (entries).readlines ()
894         file_entries = filter (cvs_entry_is_file, entries)
895         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
896         return map (lambda x: os.path.join (dir, x), files)
897
898 def flatten (tree, lst):
899         if type (tree) == type ([]):
900                 for i in tree:
901                         if type (i) == type ([]):
902                                 flatten (i, lst)
903                         else:
904                                 lst.append (i)
905         return lst
906
907 if os.path.isdir ('%(srcdir)s/CVS' % vars ()):
908         subdirs = flatten (cvs_dirs ('.'), [])
909 else:
910         # ugh
911         command = 'cd %(srcdir)s \
912         && find . -name SConscript | sed s@/SConscript@@' % vars ()
913         subdirs = string.split (os.popen (command).read ())
914
915 if env['fast']\
916    and 'all' not in COMMAND_LINE_TARGETS\
917    and 'doc' not in COMMAND_LINE_TARGETS\
918    and 'web' not in COMMAND_LINE_TARGETS\
919    and 'install' not in COMMAND_LINE_TARGETS\
920    and 'clean' not in COMMAND_LINE_TARGETS:
921         subdirs = ['lily',
922                    'flower',
923                    'kpath-guile',
924                    'mf',
925                    'python',
926                    ]
927
928 if os.path.isdir ('%(srcdir)s/CVS' % vars ()):
929         src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
930 else:
931         src_files = ['foobar']
932
933 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
934 txt_files = map (lambda x: x + '.txt', readme_files)
935
936
937 #
938 # speeds up build by +- 5% 
939
940 if not env['fast']:
941         foo = map (lambda x: env.TXT (x + '.txt',
942                                       os.path.join ('Documentation/topdocs', x)),
943                    readme_files)
944         tar_base = package.name + '-' + version
945         tar_name = tar_base + '.tar.gz'
946         ball_prefix = os.path.join (outdir, tar_base)
947         tar_ball = os.path.join (outdir, tar_name)
948
949         dist_files = src_files + txt_files
950         ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
951         map (lambda x: env.Depends (tar_ball, x), ball_files)
952         map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
953                                     'ln $SOURCE $TARGET'), dist_files)
954         tar = env.Command (tar_ball, src_files,
955                            ['rm -f $$(find $TARGET.dir -name .sconsign)',
956                             'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
957         env.Alias ('tar', tar)
958
959         dist_ball = os.path.join (package.release_dir, tar_name)
960         env.Command (dist_ball, tar_ball,
961                      'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
962                      + 'ln $SOURCE $TARGET')
963         env.Depends ('dist', dist_ball)
964         patch_name = os.path.join (outdir, tar_base + '.diff.gz')
965         patch = env.PATCH (patch_name, tar_ball)
966         env.Depends (patch_name, dist_ball)
967         env.Alias ('release', patch)
968
969 #### web
970 if not env['fast']:
971         web_base = os.path.join (outdir, 'web')
972         web_ball = web_base + '.tar.gz'
973         env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
974         web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
975         web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"') + '-or -type l'
976         env['web_path'] = web_path
977         web_list = os.path.join (outdir, 'weblist')
978         # compatible make heritits
979         # fixme: generate in $outdir is cwd/builddir
980         env.Command (web_list,
981                      ## Adding 'doc' dependency is correct, but takes
982                      ## > 5min extra if you have a peder :-)
983                      #'doc',
984                      
985                      '#/VERSION',
986                      ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
987                       'cd $absbuild && $footify $$(find . -name "*.html" -print)',
988                       'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
989                       'cd $absbuild && find Documentation input $web_path \
990                       > $TARGET',
991                       '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
992                       '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
993                       'cd $absbuild && ls *.html >> $TARGET',])
994         env.Command (web_ball, web_list,
995                      ['cat $SOURCE | tar -C $absbuild -czf $TARGET -T -',])
996         #env.Alias ('web', web_ball)
997         www_base = os.path.join (outdir, 'www')
998         www_ball = www_base + '.tar.gz'
999         env.Command (www_ball, web_ball,
1000                      ['rm -rf $out/tmp',
1001                       'mkdir -p $absbuild/$out/tmp',
1002                       'tar -C $absbuild/$out/tmp -xzf $SOURCE',
1003                       'cd $absbuild/$out/tmp && for i in $$(find . -name "$out"); '
1004                       + ' do mv $$i $$(dirname $$i)/out-www; done',
1005                       'tar -C $absbuild/$out/tmp -czf $TARGET .'])
1006         env.Alias ('web', www_ball)
1007
1008 #### tags
1009 env.Append (
1010         ETAGSFLAGS = """--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\\1/' \
1011         --regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\\1/'""")
1012 code_ext = ['.cc', '.hh', '.scm', '.tcc',]
1013 env.Command ('TAGS', filter (lambda x: os.path.splitext (x)[1] in code_ext,
1014                              src_files),
1015              'etags $ETAGSFLAGS $SOURCES')
1016
1017 # Note: SConscripts are only needed in directories where something needs
1018 # to be done, building or installing
1019 for d in subdirs:
1020         if os.path.exists (os.path.join (d, 'SConscript')):
1021                 b = os.path.join (env['build'], d, env['out'])
1022                 # Support clean sourcetree build (--srcdir build)
1023                 # and ./out build.
1024                 if os.path.abspath (b) != os.path.abspath (d):
1025                         env.BuildDir (b, d, duplicate = 0)
1026                 SConscript (os.path.join (b, 'SConscript'))
1027