]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
* SConstruct: Configure only pristine build tree or on user
[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 sys
60 import string
61
62 # SConscripts are only needed in directories where something needs
63 # to be done, building or installing
64 # TODO: Documentation/*, input/*/*, vim, po
65 # rename Documentation/* to ./doc?
66
67 subdirs = ['flower', 'lily', 'mf', 'scm', 'ly', 'Documentation',
68            'Documentation/user', 'input', 'scripts', 'elisp',
69            'buildscripts', 'cygwin', 'debian']
70
71 usage = r'''Usage:
72 scons [KEY=VALUE].. [TARGET]..
73
74 where TARGET is config|lily|all|fonts|doc|tar|dist|release
75 '''
76       
77
78 config_cache = 'config.cache'
79
80 config_vars = (
81         'BASH',
82         'CFLAGS',
83         'CPPPATH',
84         'CXXFLAGS',
85         'DEFINES',
86         'LIBS',
87         'METAFONT'
88         'PYTHON',
89         )
90
91 # Put your favourite stuff in custom.py
92 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
93 opts.Add ('prefix', 'Install prefix', '/usr/')
94 opts.Add ('out', 'Output directory', 'out-scons')
95 opts.Add ('build', 'Build directory', '.')
96 opts.AddOptions (
97         BoolOption ('warnings', 'compile with -Wall and similiar',
98                    1),
99         BoolOption ('debugging', 'compile with debugging symbols',
100                     0),
101         BoolOption ('optimising', 'compile with optimising',
102                     1),
103         BoolOption ('shared', 'build shared libraries',
104                     0),
105         BoolOption ('static', 'build static libraries',
106                     1),
107         BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
108                     1),
109         BoolOption ('verbose', 'run commands with verbose flag',
110                     0),
111         )
112
113 srcdir = Dir ('.').srcnode ().abspath
114
115 #ugh
116 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
117 import packagepython
118 package = packagepython.Package (srcdir)
119
120
121 prefix = '/usr/local'
122
123 version = packagepython.version_tuple_to_str (package.version)
124 bindir = os.path.join (prefix, 'bin')
125 sharedir = os.path.join (prefix, 'share')
126 libdir = os.path.join (prefix, 'lib')
127 localedir = os.path.join (sharedir, 'locale')
128 sharedir_package = os.path.join (sharedir, package.name)
129 sharedir_package_version = os.path.join (sharedir_package, version)
130 lilypondprefix = sharedir_package_version
131
132 ENV = { 'PATH' : os.environ['PATH'] }
133 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
134         if os.environ.has_key(key):
135                 ENV[key] = os.environ[key]
136
137 env = Environment (
138         ENV = ENV,
139
140         srcdir = srcdir,
141
142         bindir = bindir,
143         sharedir = sharedir,
144         TOPLEVEL_VERSION = version,
145         lilypond_datadir = sharedir_package,
146         local_lilypond_datadir = sharedir_package_version,
147         lilypondprefix = lilypondprefix,
148         sharedir_package = sharedir_package,
149         sharedir_package_version = sharedir_package_version,
150
151
152         SH = '/bin/sh',
153         BASH = '/bin/bash',
154         PYTHON = '/usr/bin/python',
155         MAKEINFO = 'LANG= makeinfo',
156         
157         LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
158         LILYPOND_PY = srcdir + '/scripts/lilypond.py',
159         
160         ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
161         MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
162         LILYPOND_BOOK_FLAGS = '',
163         LILYPOND_BOOK_FORMAT = 'texi-html',
164         
165         TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
166         MFMODE = 'ljfour'
167         )
168
169
170 Help (usage + opts.GenerateHelpText (env))
171
172 map (lambda x: opts.AddOptions ((x,)), config_vars)
173 opts.Update (env)
174 env.CacheDir (os.path.join (env['build'], '=build-cache'))
175
176 if env['debugging']:
177         env.Append (CFLAGS = '-g')
178         env.Append (CXXFLAGS = '-g')
179 if env['optimising']:
180         env.Append (CFLAGS = '-O2')
181         env.Append (CXXFLAGS = '-O2')
182         env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
183 if env['warnings']:
184         env.Append (CFLAGS = '-W ')
185         env.Append (CFLAGS = '-Wall')
186         # what about = ['-W', '-Wall', ...]?
187         env.Append (CXXFLAGS = '-W')
188         env.Append (CXXFLAGS = '-Wall')
189         env.Append (CXXFLAGS = '-Wconversion')
190 if env['verbose']:
191         env['__verbose'] = '--verbose'
192
193 env['srcdir'] = Dir ('.').srcnode ().abspath
194
195 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
196 config_h = os.path.join (outdir, 'config.h')
197 version_h = os.path.join (outdir, 'version.hh')
198 config_h = os.path.join (outdir, 'config.h')
199 env.Alias ('config', config_h)
200
201
202 ## Explicit dependencies
203
204 # Without target arguments, build lily only
205 if not COMMAND_LINE_TARGETS:
206         env.Default ('lily')
207 env.Alias ('all', '.')
208 env.Alias ('doc',
209            'Documentation',
210            'Documentation/user',
211            'Documentation/topdocs')
212
213 env.Depends ('doc', ['lily', 'mf'])
214 env.Depends ('input', ['lily', 'mf'])
215 env.Depends ('doc', ['lily', 'mf'])
216
217
218 def list_sort (lst):
219         sorted = lst
220         sorted.sort ()
221         return sorted
222
223 def configure (target, source, env):
224         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
225         def get_version (program):
226                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
227                 pipe = os.popen (command)
228                 output = pipe.read ()
229                 if pipe.close ():
230                         return None
231                 v = re.sub (vre, '\\1', output)
232                 return string.split (v, '.')
233
234         def test_program (lst, program, minimal, description, package):
235                 sys.stdout.write ('Checking %s version... ' % program)
236                 actual = get_version (program)
237                 if not actual:
238                         print 'not found'
239                         lst.append ((description, package, minimal, program,
240                                      'not installed'))
241                         return
242                 sys.stdout.write (string.join (actual, '.'))
243                 sys.stdout.write ('\n')
244                 if actual < string.split (minimal, '.'):
245                         lst.append ((description, package, minimal, program,
246                                      string.join (actual, '.')))
247
248         required = []
249         test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
250         test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
251         test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
252         test_program (required, 'guile-config', '1.6', 'GUILE development',
253                         'libguile-dev or guile-devel')
254         # Do not use bison 1.50 and 1.75.
255         test_program (required, 'bison', '1.25', 'Bison -- parser generator',
256                         'bison')
257         test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
258
259
260         optional = []
261         test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
262         test_program (optional, 'guile', '1.6', 'GUILE scheme',
263                         'libguile-dev or guile-devel')
264         test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
265                         'mftrace')
266         test_program (optional, 'perl', '4.0',
267                         'Perl practical efficient readonly language', 'perl')
268         #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
269
270         def CheckYYCurrentBuffer (context):
271                 context.Message ('Checking for yy_current_buffer... ')
272                 ret = conf.TryCompile ("""using namespace std;
273                 #include <FlexLexer.h>
274                 class yy_flex_lexer: public yyFlexLexer
275                 {
276                 public:
277                 yy_flex_lexer ()
278                 {
279                 yy_current_buffer = 0;
280                 }
281                 };""", '.cc')
282                 context.Result (ret)
283
284         conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
285                                                 : CheckYYCurrentBuffer })
286
287         defines = {
288            'DIRSEP' : "'/'",
289            'PATHSEP' : "':'",
290            'TOPLEVEL_VERSION' : '"' + version + '"',
291            'PACKAGE': '"' + package.name + '"',
292            'DATADIR' : '"' + sharedir + '"',
293            'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
294            'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
295            'LOCALEDIR' : '"' + localedir + '"',
296         }
297         conf.env.Append (DEFINES = defines)
298
299         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
300         PYTHON_INCLUDE = os.popen (command).read ()
301         env.Append (CPPPATH = PYTHON_INCLUDE)
302
303         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
304         for i in headers:
305                 if conf.CheckCHeader (i):
306                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
307                         conf.env['DEFINES'][key] = 1
308
309         ccheaders = ('sstream',)
310         for i in ccheaders:
311                 if conf.CheckCXXHeader (i):
312                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
313                         conf.env['DEFINES'][key] = 1
314
315         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
316         for i in functions:
317                 if 0 or conf.CheckFunc (i):
318                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
319                         conf.env['DEFINES'][key] = 1
320
321         if conf.CheckYYCurrentBuffer ():
322                 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
323
324         if conf.CheckLib ('dl'):
325                 pass
326
327         if conf.CheckLib ('kpathsea'):
328                 conf.env['DEFINES']['KPATHSEA'] = 1
329
330         # huh? 
331         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
332                 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
333         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
334                 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
335
336         #this could happen after flower...
337         env.ParseConfig ('guile-config compile')
338
339         #this could happen only for compiling pango-*
340         if env['gui']:
341                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
342                 env.ParseConfig ('pkg-config --cflags --libs pango')
343                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
344                         conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
345
346                 if conf.CheckLib ('pango-1.0',
347                                   'pango_fc_font_map_add_decoder_find_func'):
348                         conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
349                         conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
350
351         if required:
352                 print
353                 print '********************************'
354                 print 'Please install required packages'
355                 for i in required:
356                         print '%s:      %s-%s or newer (found: %s %s)' % i
357                 sys.exit (1)
358
359         if optional:
360                 print
361                 print '*************************************'
362                 print 'Consider installing optional packages'
363                 for i in optional:
364                         print '%s:      %s-%s or newer (found: %s %s)' % i
365
366         return conf.Finish ()
367
368 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
369         os.unlink (config_cache)
370 # WTF?
371 # scons: *** Calling Configure from Builders is not supported.
372 # env.Command (config_cache, None, configure)
373 if not os.path.exists (config_cache):
374         env = configure (None, None, env)
375         map (lambda x: opts.AddOptions ((x,)), config_vars)
376         opts.Save (config_cache, env)
377
378 def config_header (target, source, env):
379         config = open (str (target[0]), 'w')
380         for i in list_sort (env['DEFINES'].keys ()):
381                 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
382         config.close ()
383 env.Command (config_h, config_cache, config_header)
384
385 env.Command (version_h, '#/VERSION',
386              '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
387
388 absbuild = Dir (env['build']).abspath
389 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
390
391 env.Append (
392         absbuild = absbuild,
393         run_prefix = run_prefix,
394         LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
395
396         LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
397         CPPPATH = [outdir, '#',],
398         LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
399                                      'lilypond-bin'),
400         LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
401                               '#/input/test', '#/input/tutorial',
402                               os.path.join (absbuild, 'mf', env['out']),
403                               '#/Documentation/user',
404                               os.path.join (absbuild, 'Documentation',
405                                             env['out']),
406                               os.path.join (absbuild, 'Documentation/user',
407                                             env['out']),
408                               ],
409         MAKEINFO_PATH = ['.', '#/Documentation/user',
410                          os.path.join (absbuild, 'Documentation/user',
411                                        env['out'])],
412         )
413
414 Export ('env')
415 SConscript ('buildscripts/builder.py')
416
417
418 def symlink_tree (target, source, env):
419         def mkdirs (dir):
420                 def mkdir (dir):
421                         if not dir:
422                                 os.chdir (os.sep)
423                                 return
424                         if not os.path.isdir (dir):
425                                 if os.path.exists (dir):
426                                         os.unlink (dir)
427                                 os.mkdir (dir)
428                         os.chdir (dir)
429                 map (mkdir, string.split (dir, os.sep))
430         def symlink (src, dst):
431                 os.chdir (absbuild)
432                 dir = os.path.dirname (dst)
433                 mkdirs (dir)
434                 if src[0] == '#':
435                         frm = os.path.join (srcdir, src[1:])
436                 else:
437                         depth = len (string.split (dir, '/'))
438                         frm = os.path.join ('../' * depth, src, out)
439                 os.symlink (frm, os.path.basename (dst))
440         prefix = os.path.join (env['out'], 'usr')
441         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
442              (('python', 'lib/lilypond/python'),
443               # UGHR, lilypond.py uses lilypond-bin from PATH
444               ('lily',   'bin'),
445               ('#mf',    'share/lilypond/fonts/mf'),
446               ('mf',     'share/lilypond/fonts/afm'),
447               ('mf',     'share/lilypond/fonts/tfm'),
448               ('mf',     'share/lilypond/fonts/type1'),
449               ('#tex',   'share/lilypond/tex/source'),
450               ('mf',     'share/lilypond/tex/generate'),
451               ('#ly',    'share/lilypond/ly'),
452               ('#scm',   'share/lilypond/scm'),
453               ('#ps',    'share/lilypond/ps'),
454               ('elisp',  'share/lilypond/elisp')))
455         os.chdir (srcdir)
456
457 if env['debugging']:
458         print 'run_prefix:' + run_prefix
459         env.Command (os.path.join (run_prefix, 'stamp'), 'VERSION',
460                      [symlink_tree, 'touch $TARGET'])
461
462 #### dist, tar
463 def plus (a, b):
464         a + b
465
466 def cvs_entry_is_dir (line):
467         return line[0] == 'D' and line[-2] == '/'
468
469 def cvs_entry_is_file (line):
470         return line[0] == '/' and line[-2] == '/'
471
472 def cvs_dirs (dir):
473         ENTRIES = os.path.join (dir, 'CVS/Entries')
474         if not os.path.exists (ENTRIES):
475                 return []
476         entries = open (ENTRIES).readlines ()
477         dir_entries = filter (cvs_entry_is_dir, entries)
478         dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
479                     dir_entries)
480         return dirs + map (cvs_dirs, dirs)
481
482 def cvs_files (dir):
483         ENTRIES = os.path.join (dir, 'CVS/Entries')
484         entries = open (ENTRIES).readlines ()
485         file_entries = filter (cvs_entry_is_file, entries)
486         files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
487         return map (lambda x: os.path.join (dir, x), files)
488
489 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
490 #print `subdirs`
491 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
492 foo = map (lambda x: env.TXT (x + '.txt',
493                               os.path.join ('Documentation/topdocs', x)),
494            readme_files)
495 txt_files = map (lambda x: x + '.txt', readme_files)
496 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
497 tar_base = package.name + '-' + version
498 tar_name = tar_base + '.tar.gz'
499 ball_prefix = os.path.join (outdir, tar_base)
500 tar_ball = os.path.join (outdir, tar_name)
501
502 dist_files = src_files + txt_files
503 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
504 map (lambda x: env.Depends (tar_ball, x), ball_files)
505 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
506                             'ln $SOURCE $TARGET'), dist_files)
507 tar = env.Command (tar_ball, src_files,
508                    'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
509 env.Alias ('tar', tar)
510
511 dist_ball = os.path.join (package.release_dir, tar_name)
512 env.Command (dist_ball, tar_ball,
513              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
514              + 'ln $SOURCE $TARGET')
515 env.Depends ('dist', dist_ball)
516 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
517 patch = env.PATCH (patch_name, tar_ball)
518 env.Depends (patch_name, dist_ball)
519 env.Alias ('release', patch)
520
521
522 for d in subdirs:
523         if os.path.exists (os.path.join (d, 'SConscript')):
524                 b = os.path.join (env['build'], d, env['out'])
525                 # Support clean sourcetree build (--srcdir build)
526                 # and ./out build.
527                 if os.path.abspath (b) != os.path.abspath (d):
528                         env.BuildDir (b, d, duplicate = 0)
529                 SConscript (os.path.join (b, 'SConscript'))