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