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