]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
creates LyricText objects
[lilypond.git] / SConstruct
1 # -*-python-*-
2
3 '''
4 Experimental scons (www.scons.org) building:
5
6 scons TARGET builds from source directory ./TARGET (not recursive)
7
8
9 Usage:
10     scons
11     scons lily            # build lily
12
13     LILYPONDPREFIX=out-scons/usr/share/lilypond lily/out-scons/lilypond-bin
14     scons doc             # build web doc
15
16 ?    scons fonts           # build all font stuff (split this? )
17
18     scons config          # reconfigure
19
20     scons /               # builds all possible targets
21
22     scons install
23     scons -c              # clean
24     scons -h              # help
25
26     scons build=DIR       # scrdir build, write to new tree =build
27     scons out=DIR         # write output to deeper dir DIR
28
29 Optionally, make a custom.py.  I have
30
31 import os
32 out='out-scons'
33 optimising=0
34 debugging=1
35 gui=1
36 os.path.join (os.getcwd (), '=install')
37 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
38
39 '''
40
41
42 # TODO:
43 #   * add missing dirs
44 #   * cleanup
45
46 #   * separate environments?
47 #     - compile environment checks headers and libraries
48 #     - doc environment checks doc stuff
49
50 #   * commandline targets:
51 #      - clean => -c ?
52 #   * more fine-grained config.h -- move lilypondprefix to version.hh?
53 #     - config.h:   changes after system upgrades, affects all files
54 #     - version.hh:  prefix, version etc?  affects few
55
56 import re
57 import glob
58 import os
59 import string
60 import sys
61 import stat
62 import shutil
63
64 # faster but scary: when changing #includes, do scons --implicit-deps-changed
65 # SetOption ('implicit_cache', 1)
66
67 # SConscripts are only needed in directories where something needs
68 # to be done, building or installing
69 # TODO: Documentation/*, input/*/*, vim, po
70 # rename Documentation/* to ./doc?
71
72 # somethin's broken wrt config.h checksums?
73 FOOSUMS = 1
74
75 subdirs = ['flower', 'lily', 'mf', 'scm', 'ly', 'Documentation',
76            'Documentation/user', 'Documentation/topdocs',
77            'input', 'scripts', 'elisp',
78            'buildscripts', 'cygwin', 'debian', 'po']
79
80 usage = r'''Usage:
81 scons [KEY=VALUE].. [TARGET]..
82
83 where TARGET is config|lily|all|fonts|doc|tar|dist|release
84 '''
85       
86
87 config_cache = 'config.cache'
88
89 config_vars = (
90         'BASH',
91         'CFLAGS',
92         'CPPPATH',
93         'CXXFLAGS',
94         'DEFINES',
95         'LIBS',
96         'METAFONT',
97         'PERL',
98         'PYTHON',
99         )
100
101 # Put your favourite stuff in custom.py
102 CacheDir ("buildcache")
103 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
104 opts.Add ('prefix', 'Install prefix', '/usr/')
105 opts.Add ('out', 'Output directory', 'out-scons')
106 opts.Add ('build', 'Build directory', '.')
107 opts.AddOptions (
108         BoolOption ('warnings', 'compile with -Wall and similiar',
109                    1),
110         BoolOption ('debugging', 'compile with debugging symbols',
111                     0),
112         BoolOption ('optimising', 'compile with optimising',
113                     1),
114         BoolOption ('shared', 'build shared libraries',
115                     0),
116         BoolOption ('static', 'build static libraries',
117                     1),
118         BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
119                     1),
120         BoolOption ('verbose', 'run commands with verbose flag',
121                     0),
122         BoolOption ('checksums', 'use checksums instead of timestamps',
123                     1),
124         )
125
126 srcdir = Dir ('.').srcnode ().abspath
127
128 #ugh
129 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
130 import packagepython
131 package = packagepython.Package (srcdir)
132 version = packagepython.version_tuple_to_str (package.version)
133
134 ENV = { 'PATH' : os.environ['PATH'] }
135 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
136         if os.environ.has_key(key):
137                 ENV[key] = os.environ[key]
138
139 env = Environment (
140         ENV = ENV,
141
142         BASH = '/bin/bash',
143         MAKEINFO = 'LANG= makeinfo',
144         PERL = '/usr/bin/perl',
145         PYTHON = '/usr/bin/python',
146         SH = '/bin/sh',
147         
148         ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
149         LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
150         LILYPOND_BOOK_FLAGS = '',
151         LILYPOND_BOOK_FORMAT = 'texi-html',
152         LILYPOND_PY = srcdir + '/scripts/lilypond.py',
153         MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
154         
155         PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
156                                          'usr/pkg/gnome/lib'),
157                            os.path.join (os.environ['HOME'],
158                                          'usr/pkg/pango/lib')],
159         MFMODE = 'ljfour',
160         TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
161         TOPLEVEL_VERSION = version,
162         )
163
164 Help (usage + opts.GenerateHelpText (env))
165
166 map (lambda x: opts.AddOptions ((x,)), config_vars)
167 opts.Update (env)
168
169 if env['checksums']:
170         SetOption ('max_drift', 0)
171
172 prefix = env['prefix']
173 bindir = os.path.join (prefix, 'bin')
174 sharedir = os.path.join (prefix, 'share')
175 libdir = os.path.join (prefix, 'lib')
176 localedir = os.path.join (sharedir, 'locale')
177 sharedir_package = os.path.join (sharedir, package.name)
178 sharedir_package_version = os.path.join (sharedir_package, version)
179 lilypondprefix = sharedir_package_version
180
181 # post-option environment-update
182 env.Append (
183         srcdir = srcdir,
184         
185         bindir = bindir,
186         sharedir = sharedir,
187         lilypond_datadir = sharedir_package,
188         localedir = localedir,
189         local_lilypond_datadir = sharedir_package_version,
190         lilypondprefix = lilypondprefix,
191         sharedir_package = sharedir_package,
192         sharedir_package_version = sharedir_package_version,
193         )
194
195 env.CacheDir (os.path.join (env['build'], '=build-cache'))
196
197 if env['debugging']:
198         env.Append (CFLAGS = '-g')
199         env.Append (CXXFLAGS = '-g')
200 if env['optimising']:
201         env.Append (CFLAGS = '-O2')
202         env.Append (CXXFLAGS = '-O2')
203         env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
204 if env['warnings']:
205         env.Append (CFLAGS = '-W ')
206         env.Append (CFLAGS = '-Wall')
207         # what about = ['-W', '-Wall', ...]?
208         env.Append (CXXFLAGS = '-W')
209         env.Append (CXXFLAGS = '-Wall')
210         env.Append (CXXFLAGS = '-Wconversion')
211 if env['verbose']:
212         env['__verbose'] = '--verbose'
213
214 # Hmm
215 #env.Append (ENV = {'PKG_CONFIG_PATH' : string.join (env['PKG_CONFIG_PATH'],
216 #                                                   os.pathsep),
217 #                  'LD_LIBRARY_PATH' : string.join (env['LD_LIBRARY_PATH'],
218 #                                                   os.pathsep),
219 #                  'GUILE_LOAD_PATH' : string.join (env['GUILE_LOAD_PATH'],
220 #                                                   os.pathsep), })
221
222 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
223
224 # This is interesting, version.hh is generated automagically, just in
225 # time.  Is this a .h /.hh issue?  It seems to be, using config.hh (in
226 # flower/file-name.cc) works.  Bug report to SCons or rename to
227 # config.hh or both?
228 # config_h = os.path.join (outdir, 'config.hh')
229 config_h = os.path.join (outdir, 'config.h')
230 version_h = os.path.join (outdir, 'version.hh')
231
232 env.Alias ('config', config_cache)
233
234
235 ## Explicit dependencies
236
237 # Without target arguments, build lily only
238 if not COMMAND_LINE_TARGETS:
239         env.Default ('lily')
240 env.Alias ('all', '.')
241 env.Alias ('doc',
242            ['Documentation',
243             'Documentation/user',
244             'Documentation/topdocs'])
245
246 env.Depends (['lily', 'flower', 'all', '.'], config_h)
247 env.Depends ('doc', ['lily', 'mf'])
248 env.Depends ('input', ['lily', 'mf'])
249 env.Depends ('doc', ['lily', 'mf'])
250
251
252 def list_sort (lst):
253         sorted = lst
254         sorted.sort ()
255         return sorted
256
257 def configure (target, source, env):
258         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
259         def get_version (program):
260                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
261                 pipe = os.popen (command)
262                 output = pipe.read ()
263                 if pipe.close ():
264                         return None
265                 v = re.sub (vre, '\\1', output)
266                 return string.split (v, '.')
267
268         def test_program (lst, program, minimal, description, package):
269                 sys.stdout.write ('Checking %s version... ' % program)
270                 actual = get_version (program)
271                 if not actual:
272                         print 'not found'
273                         lst.append ((description, package, minimal, program,
274                                      'not installed'))
275                         return
276                 sys.stdout.write (string.join (actual, '.'))
277                 sys.stdout.write ('\n')
278                 if actual < string.split (minimal, '.'):
279                         lst.append ((description, package, minimal, program,
280                                      string.join (actual, '.')))
281
282         required = []
283         test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
284         test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
285         test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
286         test_program (required, 'guile-config', '1.6', 'GUILE development',
287                         'libguile-dev or guile-devel')
288         # Do not use bison 1.50 and 1.75.
289         test_program (required, 'bison', '1.25', 'Bison -- parser generator',
290                         'bison')
291         test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
292
293
294         optional = []
295         test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
296         test_program (optional, 'guile', '1.6', 'GUILE scheme',
297                         'libguile-dev or guile-devel')
298         test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
299                         'mftrace')
300         test_program (optional, 'perl', '4.0',
301                         'Perl practical efficient readonly language', 'perl')
302         #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
303
304         def CheckYYCurrentBuffer (context):
305                 context.Message ('Checking for yy_current_buffer... ')
306                 ret = conf.TryCompile ("""using namespace std;
307                 #include <FlexLexer.h>
308                 class yy_flex_lexer: public yyFlexLexer
309                 {
310                 public:
311                 yy_flex_lexer ()
312                 {
313                 yy_current_buffer = 0;
314                 }
315                 };""", '.cc')
316                 context.Result (ret)
317                 return ret
318
319         conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
320                                                 : CheckYYCurrentBuffer })
321
322         defines = {
323            'DIRSEP' : "'/'",
324            'PATHSEP' : "':'",
325            'TOPLEVEL_VERSION' : '"' + version + '"',
326            'PACKAGE': '"' + package.name + '"',
327            'DATADIR' : '"' + sharedir + '"',
328            'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
329            'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
330            'LOCALEDIR' : '"' + localedir + '"',
331         }
332         conf.env.Append (DEFINES = defines)
333
334         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
335         PYTHON_INCLUDE = os.popen (command).read ()
336         env.Append (CPPPATH = PYTHON_INCLUDE)
337
338         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
339         for i in headers:
340                 if conf.CheckCHeader (i):
341                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
342                         conf.env['DEFINES'][key] = 1
343
344         ccheaders = ('sstream',)
345         for i in ccheaders:
346                 if conf.CheckCXXHeader (i):
347                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
348                         conf.env['DEFINES'][key] = 1
349
350         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
351         for i in functions:
352                 if 0 or conf.CheckFunc (i):
353                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
354                         conf.env['DEFINES'][key] = 1
355
356         if conf.CheckYYCurrentBuffer ():
357                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
358
359         if conf.CheckLib ('dl'):
360                 pass
361
362         if conf.CheckLib ('kpathsea'):
363                 conf.env['DEFINES']['KPATHSEA'] = 1
364
365         # huh? 
366         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
367                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
368         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
369                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
370
371         #this could happen after flower...
372         env.ParseConfig ('guile-config compile')
373
374         #this could happen only for compiling pango-*
375         if env['gui']:
376                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
377                 env.ParseConfig ('pkg-config --cflags --libs pango')
378                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
379                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
380
381                 if conf.CheckLib ('pango-1.0',
382                                   'pango_fc_font_map_add_decoder_find_func'):
383                         conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
384                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
385
386         if required:
387                 print
388                 print '********************************'
389                 print 'Please install required packages'
390                 for i in required:
391                         print '%s:      %s-%s or newer (found: %s %s)' % i
392                 sys.exit (1)
393
394         if optional:
395                 print
396                 print '*************************************'
397                 print 'Consider installing optional packages'
398                 for i in optional:
399                         print '%s:      %s-%s or newer (found: %s %s)' % i
400
401         return conf.Finish ()
402
403 def config_header (target, source, env):
404         config = open (str (target[0]), 'w')
405         for i in list_sort (env['DEFINES'].keys ()):
406                 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
407         config.close ()
408
409 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
410         os.unlink (config_cache)
411 # WTF?
412 # scons: *** Calling Configure from Builders is not supported.
413 # env.Command (config_cache, None, configure)
414 if not os.path.exists (config_cache) \
415    or (os.stat ('SConstruct')[stat.ST_MTIME]
416        > os.stat (config_cache)[stat.ST_MTIME]):
417         env = configure (None, None, env)
418
419         # We 'should' save opts each time, but that makes config.h
420         # always out of date, and that triggers recompiles, even when
421         # using checksums?
422         if FOOSUMS: #not env['checksums']:
423                 ## FIXME: Is this smart, using option cache for saving
424                 ## config.cache?  I cannot seem to find the official method.
425                 map (lambda x: opts.AddOptions ((x,)), config_vars)
426                 opts.Save (config_cache, env)
427                 env.Command (config_h, config_cache, config_header)
428
429 # hmm?
430 def xuniquify (lst):
431         n = []
432         for i in lst:
433                 if not i in n:
434                         n.append (i)
435         lst = n
436         return lst
437
438 def uniquify (lst):
439         d = {}
440         n = len (lst)
441         i = 0
442         while i < n:
443                 if not d.has_key (lst[i]):
444                         d[lst[i]] = 1
445                         i += 1
446                 else:
447                         del lst[i]
448                         n -= 1
449         return lst
450
451 for i in config_vars:
452         if env.has_key (i) and type (env[i]) == type ([]):
453                 env[i] = uniquify (env[i])
454
455 if not FOOSUMS: #env['checksums']:
456         ## FIXME: Is this smart, using option cache for saving
457         ## config.cache?  I cannot seem to find the official method.
458         map (lambda x: opts.AddOptions ((x,)), config_vars)
459         
460         opts.Save (config_cache, env)
461         env.Command (config_h, config_cache, config_header)
462
463 env.Command (version_h, '#/VERSION',
464              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
465
466 absbuild = Dir (env['build']).abspath
467 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
468
469 # post-config environment update
470 env.Append (
471         absbuild = absbuild,
472         run_prefix = run_prefix,
473         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
474
475         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
476         CPPPATH = [outdir, '#',],
477         LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
478                                      'lilypond-bin'),
479         LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
480                               '#/input/test', '#/input/tutorial',
481                               os.path.join (absbuild, 'mf', env['out']),
482                               '#/Documentation/user',
483                               os.path.join (absbuild, 'Documentation',
484                                             env['out']),
485                               os.path.join (absbuild, 'Documentation/user',
486                                             env['out']),
487                               ],
488         MAKEINFO_PATH = ['.', '#/Documentation/user',
489                          os.path.join (absbuild, 'Documentation/user',
490                                        env['out'])],
491         )
492
493 Export ('env')
494 SConscript ('buildscripts/builder.py')
495
496
497 def symlink_tree (target, source, env):
498         def mkdirs (dir):
499                 def mkdir (dir):
500                         if not dir:
501                                 os.chdir (os.sep)
502                                 return
503                         if not os.path.isdir (dir):
504                                 if os.path.exists (dir):
505                                         os.unlink (dir)
506                                 os.mkdir (dir)
507                         os.chdir (dir)
508                 map (mkdir, string.split (dir, os.sep))
509         def symlink (src, dst):
510                 os.chdir (absbuild)
511                 dir = os.path.dirname (dst)
512                 mkdirs (dir)
513                 if src[0] == '#':
514                         frm = os.path.join (srcdir, src[1:])
515                 else:
516                         depth = len (string.split (dir, '/'))
517                         frm = os.path.join ('../' * depth, src, env['out'])
518                 os.symlink (frm, os.path.basename (dst))
519         shutil.rmtree (run_prefix)
520         prefix = os.path.join (env['out'], 'usr')
521         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
522              (('python', 'lib/lilypond/python'),
523               # UGHR, lilypond.py uses lilypond-bin from PATH
524               ('lily',   'bin'),
525               ('#mf',    'share/lilypond/fonts/mf'),
526               ('mf',     'share/lilypond/fonts/afm'),
527               ('mf',     'share/lilypond/fonts/tfm'),
528               ('mf',     'share/lilypond/fonts/type1'),
529               ('#tex',   'share/lilypond/tex/source'),
530               ('mf',     'share/lilypond/tex/generate'),
531               ('#ly',    'share/lilypond/ly'),
532               ('#scm',   'share/lilypond/scm'),
533               ('#ps',    'share/lilypond/ps'),
534               ('elisp',  'share/lilypond/elisp')))
535         os.chdir (srcdir)
536
537 if env['debugging']:
538         stamp = os.path.join (run_prefix, 'stamp')
539         env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
540         env.Depends ('lily', stamp)
541
542 #### dist, tar
543 def plus (a, b):
544         a + b
545
546 def cvs_entry_is_dir (line):
547         return line[0] == 'D' and line[-2] == '/'
548
549 def cvs_entry_is_file (line):
550         return line[0] == '/' and line[-2] == '/'
551
552 def cvs_dirs (dir):
553         ENTRIES = os.path.join (dir, 'CVS/Entries')
554         if not os.path.exists (ENTRIES):
555                 return []
556         entries = open (ENTRIES).readlines ()
557         dir_entries = filter (cvs_entry_is_dir, entries)
558         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
559                     dir_entries)
560         return dirs + map (cvs_dirs, dirs)
561
562 def cvs_files (dir):
563         ENTRIES = os.path.join (dir, 'CVS/Entries')
564         entries = open (ENTRIES).readlines ()
565         file_entries = filter (cvs_entry_is_file, entries)
566         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
567         return map (lambda x: os.path.join (dir, x), files)
568
569 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
570 #print `subdirs`
571 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
572 foo = map (lambda x: env.TXT (x + '.txt',
573                               os.path.join ('Documentation/topdocs', x)),
574            readme_files)
575 txt_files = map (lambda x: x + '.txt', readme_files)
576 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
577 tar_base = package.name + '-' + version
578 tar_name = tar_base + '.tar.gz'
579 ball_prefix = os.path.join (outdir, tar_base)
580 tar_ball = os.path.join (outdir, tar_name)
581
582 dist_files = src_files + txt_files
583 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
584 map (lambda x: env.Depends (tar_ball, x), ball_files)
585 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
586                             'ln $SOURCE $TARGET'), dist_files)
587 tar = env.Command (tar_ball, src_files,
588                    'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
589 env.Alias ('tar', tar)
590
591 dist_ball = os.path.join (package.release_dir, tar_name)
592 env.Command (dist_ball, tar_ball,
593              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
594              + 'ln $SOURCE $TARGET')
595 env.Depends ('dist', dist_ball)
596 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
597 patch = env.PATCH (patch_name, tar_ball)
598 env.Depends (patch_name, dist_ball)
599 env.Alias ('release', patch)
600
601 for d in subdirs:
602         if os.path.exists (os.path.join (d, 'SConscript')):
603                 b = os.path.join (env['build'], d, env['out'])
604                 # Support clean sourcetree build (--srcdir build)
605                 # and ./out build.
606                 if os.path.abspath (b) != os.path.abspath (d):
607                         env.BuildDir (b, d, duplicate = 0)
608                 SConscript (os.path.join (b, 'SConscript'))