]> git.donarmstrong.com Git - lilypond.git/blob - SConstruct
Updates. Add targets: tar, dist, release.
[lilypond.git] / SConstruct
1 # -*-python-*-
2
3 '''
4 Experimental scons (www.scons.org) building:
5
6 Usage:
7     scons
8     scons lily            # build lily
9
10     LILYPONDPREFIX=out-scons/usr/share/lilypond lily/out-scons/lilypond-bin
11     scons doc             # build web doc
12
13     scons fonts           # build all font stuff (split this? )
14
15     scons config          # reconfigure
16
17     scons /               # builds all possible targets
18
19     scons install
20     scons -c              # clean
21     scons -h              # help
22
23     scons build=DIR       # scrdir build, write to new tree =build
24     scons out=DIR         # write output to deeper dir DIR
25
26 Optionally, make a custom.py.  I have
27
28 import os
29 out='out-scons'
30 optimising=0
31 debugging=1
32 gui=1
33 os.path.join (os.getcwd (), '=install')
34 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
35
36 '''
37
38
39 # TODO:
40 #   * add missing dirs
41 #   * cleanup
42
43 #   * separate environments?
44 #     - compile environment checks headers and libraries
45 #     - doc environment checks doc stuff
46
47 #   * commandline targets:
48 #      - clean => -c ?
49 #   * more fine-grained config.h -- move lilypondprefix to version.hh?
50 #     - config.h:   changes after system upgrades, affects all files
51 #     - version.hh:  prefix, version etc?  affects few
52
53 import re
54 import glob
55 import os
56 import sys
57 import string
58
59 subdirs = ['flower', 'lily', 'mf', 'scm', 'ly', 'Documentation',
60            'Documentation/user', 'input']
61
62 usage = r'''Usage:
63 scons [KEY=VALUE].. [TARGET]..
64
65 where TARGET is config|lily|all|fonts|doc|tar|dist|release
66 '''
67       
68 env = Environment ()
69
70 # Without target arguments, build lily only
71 if not COMMAND_LINE_TARGETS:
72         env.Default ('lily')
73
74 # All builds everything (all directories)
75 env.Alias ('all', ['lily', 'mf', 'input', 'Documentation'])
76
77
78 ## FIXME: opts in function
79
80 # Put your favourite stuff in custom.py
81 opts = Options (['config.cache', 'custom.py'], ARGUMENTS)
82 opts.Add ('prefix', 'Install prefix', '/usr/')
83 opts.Add ('out', 'Output directory', 'out-scons')
84 opts.Add ('build', 'Build directory', '.')
85 opts.AddOptions (
86         BoolOption ('warnings', 'compile with -Wall and similiar',
87                    1),
88         BoolOption ('debugging', 'compile with debugging symbols',
89                     0),
90         BoolOption ('optimising', 'compile with optimising',
91                     1),
92         BoolOption ('shared', 'build shared libraries',
93                     0),
94         BoolOption ('static', 'build static libraries',
95                     1),
96         BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
97                     1),
98         BoolOption ('verbose', 'run commands with verbose flag',
99                     0),
100         )
101
102 Help (usage + opts.GenerateHelpText (env))
103
104 env = Environment (options = opts)
105
106 opts.Update (env)
107 opts.Save ('config.cache', env)
108
109 env.CacheDir (os.path.join (env['build'], '=build-cache'))
110
111 #ugh
112 sys.path.append (os.path.join ('.', 'stepmake', 'bin'))
113 import packagepython
114 package = packagepython.Package ('.')
115
116 env['version'] = packagepython.version_tuple_to_str (package.version)
117 env['bindir'] = os.path.join (env['prefix'], 'bin')
118 env['sharedir'] = os.path.join (env['prefix'], 'share')
119 env['libdir'] = os.path.join (env['prefix'], 'lib')
120 env['localedir'] = os.path.join (env['sharedir'], 'locale')
121
122 env['sharedir_package'] = os.path.join (env['sharedir'], package.name)
123 env['sharedir_package_version'] = os.path.join (env['sharedir_package'],
124                                                  env['version'])
125 env['lilypondprefix'] = os.path.join (env['sharedir_package_version'])
126
127
128 if env['debugging']:
129         env.Append (CFLAGS = '-g')
130         env.Append (CXXFLAGS = '-g')
131 if env['optimising']:
132         env.Append (CFLAGS = '-O2')
133         env.Append (CXXFLAGS = '-O2')
134         env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
135 if env['warnings']:
136         env.Append (CFLAGS = '-W ')
137         env.Append (CFLAGS = '-Wall')
138         # what about = ['-W', '-Wall', ...]?
139         env.Append (CXXFLAGS = '-W')
140         env.Append (CXXFLAGS = '-Wall')
141         env.Append (CXXFLAGS = '-Wconversion')
142
143
144
145 ##Import ('env')
146 here = os.getcwd ()
147 reldir = str (Dir ('.').srcnode ())
148 os.chdir (reldir)
149 srcdir = os.getcwd ()
150 os.chdir (here)
151 ##outdir = os.path.join (env['build'], reldir, env['out'])
152 outdir = os.path.join (env['build'], env['out'])
153
154 env['srcdir'] = srcdir
155 build = env['build']
156 out = env['out']
157
158
159 def list_sort (lst):
160         sorted = lst
161         sorted.sort ()
162         return sorted
163
164 env['MFMODE'] = 'ljfour'
165 config_h = os.path.join (outdir, 'config.h')
166 env.Alias ('config', config_h)
167
168 def configure (env):
169         conf = Configure (env)
170
171         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
172         def get_version (program):
173                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
174                 pipe = os.popen (command)
175                 output = pipe.read ()
176                 if pipe.close ():
177                         return None
178                 v = re.sub (vre, '\\1', output)
179                 return string.split (v, '.')
180
181         def assert_version (lst, program, minimal, description, package):
182                 global required
183                 sys.stdout.write ('Checking %s version... ' % program)
184                 actual = get_version (program)
185                 if not actual:
186                         print 'not found'
187                         lst.append ((description, package, minimal, program,
188                                      'not installed'))
189                         return
190                 sys.stdout.write (string.join (actual, '.'))
191                 sys.stdout.write ('\n')
192                 if actual < string.split (minimal, '.'):
193                         lst.append ((description, package, minimal, program,
194                                      string.join (actual, '.')))
195
196         required = []
197         assert_version (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
198         assert_version (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
199         assert_version (required, 'python', '2.1', 'Python (www.python.org)', 'python')
200         assert_version (required, 'guile-config', '1.6', 'GUILE development',
201                         'libguile-dev or guile-devel')
202         # Do not use bison 1.50 and 1.75.
203         assert_version (required, 'bison', '1.25', 'Bison -- parser generator',
204                         'bison')
205         assert_version (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
206
207
208         optional = []
209         assert_version (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
210         assert_version (optional, 'guile', '1.6', 'GUILE scheme',
211                         'libguile-dev or guile-devel')
212         assert_version (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
213                         'mftrace')
214         assert_version (optional, 'perl', '4.0',
215                         'Perl practical efficient readonly language', 'perl')
216         #assert_version (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
217
218
219         defines = {
220            'DIRSEP' : "'/'",
221            'PATHSEP' : "':'",
222            'TOPLEVEL_VERSION' : '"' + env['version'] + '"',
223            'PACKAGE': '"' + package.name + '"',
224            'DATADIR' : '"' + env['sharedir'] + '"',
225            'LILYPOND_DATADIR' : '"' + env['sharedir_package'] + '"',
226            'LOCAL_LILYPOND_DATADIR' : '"' + env['sharedir_package_version'] + '"',
227            'LOCALEDIR' : '"' + env['localedir'] + '"',
228         }
229
230
231         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
232         PYTHON_INCLUDE = os.popen (command).read ()
233         env.Append (CPPPATH = PYTHON_INCLUDE)
234
235         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
236         for i in headers:
237                 if conf.CheckCHeader (i):
238                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
239                         defines[key] = '1'
240
241         ccheaders = ('sstream',)
242         for i in ccheaders:
243                 if conf.CheckCXXHeader (i):
244                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
245                         defines[key] = '1'
246
247         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
248         for i in functions:
249                 if 0 or conf.CheckFunc (i):
250                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
251                         defines[key] = '1'
252
253         key = 'HAVE_FLEXLEXER_YY_CURRENT_BUFFER'
254
255         sys.stdout.write('Checking for yy_current_buffer ... ')
256         sys.stdout.flush()
257         res = conf.TryCompile ("""using namespace std;
258         #include <FlexLexer.h>
259         class yy_flex_lexer: public yyFlexLexer
260         {
261           public:
262             yy_flex_lexer ()
263             {
264               yy_current_buffer = 0;
265             }
266         };""", '.cc')
267         if res:
268                 defines[key] = '1'
269                 sys.stdout.write('yes\n')
270         else:
271                 sys.stdout.write('no\n')
272
273
274         if conf.CheckLib ('dl'):
275                 pass
276
277         if conf.CheckLib ('kpathsea'):
278                 defines['KPATHSEA'] = '1'
279
280         # huh? 
281         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
282                 defines['HAVE_KPSE_FIND_FILE'] = '1'
283         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
284                 defines['HAVE_KPSE_FIND_TFM'] = '1'
285
286         #this could happen after flower...
287         env.ParseConfig ('guile-config compile')
288
289         #this could happen only for compiling pango-*
290         if env['gui']:
291                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
292                 env.ParseConfig ('pkg-config --cflags --libs pango')
293                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
294                         defines['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
295
296                 if conf.CheckLib ('pango-1.0',
297                                   'pango_fc_font_map_add_decoder_find_func'):
298                         defines['HAVE_PANGO_CVS'] = '1'
299                         defines['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
300
301         # ugh - needed at all?  make Builder/Command for config.h!
302         if not os.path.exists (outdir):
303                 os.mkdir (outdir)
304
305         config = open (config_h, 'w')
306         for i in list_sort (defines.keys ()):
307                 config.write ('#define %s %s\n' % (i, defines[i]))
308         config.close ()
309
310
311         os.system (sys.executable \
312                    + ' ./stepmake/bin/make-version.py VERSION > '\
313                    + os.path.join (outdir, 'version.hh'))
314
315         if required:
316                 print
317                 print '********************************'
318                 print 'Please install required packages'
319                 for i in required:
320                         print '%s:      %s-%s or newer (found: %s %s)' % i
321                 sys.exit (1)
322
323         if optional:
324                 print
325                 print '*************************************'
326                 print 'Consider installing optional packages'
327                 for i in optional:
328                         print '%s:      %s-%s or newer (found: %s %s)' % i
329
330         return conf.Finish ()
331
332 # Hmm.  Must configure when building lily, to get compiler and linker
333 # flags set-up.
334 # FIXME
335 if not os.path.exists (config_h) or 'config' in COMMAND_LINE_TARGETS\
336    or 'lily' in BUILD_TARGETS or 'all' in BUILD_TARGETS:
337         env = configure (env)
338
339 if os.path.exists ('parser'):
340         env.Append (LIBPATH = ['#/flower', '#/lily', '#/parser', '#/gui',],
341                     CPPPATH = [outdir, '#',])
342 else:   
343         env.Append (LIBPATH = ['#/flower/' + out,],
344                     CPPPATH = [outdir, '#',])
345
346 Export ('env')
347
348 #ugr
349 if build == '.':
350         absbuild = os.getcwd ()
351 else:
352         absbuild = build
353 env['absbuild'] = absbuild
354
355 # duh
356 env['MAKEINFO'] = 'LANG= makeinfo'
357 env['PYTHON'] = 'python'
358 env['LILYPOND_BIN'] = os.path.join (absbuild, 'lily', out, 'lilypond-bin')
359 env['LILYPONDPREFIX'] = os.path.join (outdir, 'usr/share/lilypond')
360 env['LILYPOND_BOOK'] = srcdir + '/scripts/lilypond-book.py'
361 env['ABC2LY_PY'] = srcdir + '/scripts/abc2ly.py'
362 env['MF_TO_TABLE_PY'] = srcdir + '/buildscripts/mf-to-table.py'
363 env['LILYPOND_PY'] = srcdir + '/scripts/lilypond.py'
364 env['LILYPOND_BOOK_FLAGS'] = ''
365 env['LILYPOND_BOOK_FORMAT'] = 'texi-html'
366 # ugh?
367 env['LILYPOND_BOOK_PATH'] = ['.', '#/input', '#/input/regression',
368                              '#/input/test', '#/input/tutorial',
369                              os.path.join (absbuild, 'mf', out),
370                              '#/Documentation/user',
371                              os.path.join (absbuild, 'Documentation', out),
372                              os.path.join (absbuild, 'Documentation/user', out),
373                              ]
374                              
375 env['MAKEINFO_PATH'] = ['.', '#/Documentation/user',
376                         os.path.join (absbuild, 'Documentation/user', out)]
377
378 ## TEXINFO_PAPERSIZE_OPTION= $(if $(findstring $(PAPERSIZE),a4),,-t @afourpaper)
379 env['TEXINFO_PAPERSIZE_OPTION'] = '-t @afourpaper'
380
381 SConscript ('buildscripts/builder.py')
382
383 for d in subdirs:
384         b = os.path.join (build, d, out)
385         # Support clean sourctree build (srcdir build)
386         # and outdir build.
387         # TODO: figure out SConscript (dir, builddir, duplicate)) feature
388         if (build and build != '.') \
389            or (out and out != '.'):
390                 env.BuildDir (b, d, duplicate=0)
391         SConscript (os.path.join (b, 'SConscript'))
392
393 # as a builder?
394 def symlink_tree (prefix):
395         def mkdirs (dir):
396                 def mkdir (dir):
397                         if not dir:
398                                 os.chdir (os.sep)
399                                 return
400                         if not os.path.isdir (dir):
401                                 if os.path.exists (dir):
402                                         os.unlink (dir)
403                                 os.mkdir (dir)
404                         os.chdir (dir)
405                 map (mkdir, string.split (dir, os.sep))
406         def symlink (src, dst):
407                 os.chdir (absbuild)
408                 dir = os.path.dirname (dst)
409                 mkdirs (dir)
410                 if src[0] == '#':
411                         frm = os.path.join (srcdir, src[1:])
412                 else:
413                         depth = len (string.split (dir, '/'))
414                         frm = os.path.join ('../' * depth, src, out)
415                 os.symlink (frm, os.path.basename (dst))
416         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
417              (('python', 'lib/lilypond/python'),
418               # UGHR, lilypond.py uses lilypond-bin from PATH
419               ('lily',   'bin'),
420               ('#mf',    'share/lilypond/fonts/mf'),
421               ('mf',     'share/lilypond/fonts/afm'),
422               ('mf',     'share/lilypond/fonts/tfm'),
423               ('mf',     'share/lilypond/fonts/type1'),
424               ('#tex',   'share/lilypond/tex/source'),
425               ('mf',     'share/lilypond/tex/generate'),
426               ('#ly',    'share/lilypond/ly'),
427               ('#scm',   'share/lilypond/scm'),
428               ('#ps',    'share/lilypond/ps'),
429               ('elisp',  'share/lilypond/elisp')))
430         os.chdir (srcdir)
431
432 if env['debugging']:
433         prefix = os.path.join (out, 'usr')
434         if not os.path.exists (prefix):
435                 symlink_tree (prefix)
436
437 #### dist, tar
438 src_files = ['ChangeLog', '.cvsignore', 'Documentation/index.html.in',
439              'lily/beam.cc']
440
441 def cvs_files (dir):
442         entries = open (os.path.join (dir, 'CVS/Entries')).readlines ()
443         files = filter (lambda x: x[0] != 'D', entries)
444         return map (lambda x: os.path.join (dir, x[1:x[1:].index ('/')+1]),
445                     files)
446
447 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
448 foo = map (lambda x: env.Texi2txt (x + '.txt',
449                                    os.path.join ('Documentation/topdocs',
450                                                  x)),
451            readme_files)
452 txt_files = map (lambda x: x + '.txt', readme_files)
453 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
454 tar_base = package.name + '-' + env['version']
455 tar_name = tar_base + '.tar.gz'
456 ball_prefix = os.path.join (outdir, tar_base)
457 tar_ball = os.path.join (outdir, tar_name)
458
459 dist_files = src_files + txt_files
460 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
461 map (lambda x: env.Depends (tar_ball, x), ball_files)
462 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
463                             'ln $SOURCE $TARGET'), dist_files)
464 tar = env.Command (tar_ball, src_files,
465                    'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
466 env.Alias ('tar', tar)
467
468 dist_ball = os.path.join (package.release_dir, tar_name)
469 env.Command (dist_ball, tar_ball,
470              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
471              + 'ln $SOURCE $TARGET')
472 env.Depends ('dist', dist_ball)
473 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
474 patch = env.PATCH (patch_name, tar_ball)
475 env.Depends (patch_name, dist_ball)
476 env.Alias ('release', patch)
477