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