]> 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 env.Depends ('Documentation', ['lily', 'mf'])
79 env.Depends ('input', ['lily', 'mf'])
80 env.Depends ('doc', ['lily', 'mf'])
81 env.Depends ('doc', 'mf')
82 env.Depends ('input', ['lily', 'mf'])
83
84
85 ## FIXME: opts in function
86
87 # Put your favourite stuff in custom.py
88 opts = Options (['config.cache', 'custom.py'], ARGUMENTS)
89 opts.Add ('prefix', 'Install prefix', '/usr/')
90 opts.Add ('out', 'Output directory', 'out-scons')
91 opts.Add ('build', 'Build directory', '.')
92 opts.AddOptions (
93         BoolOption ('warnings', 'compile with -Wall and similiar',
94                    1),
95         BoolOption ('debugging', 'compile with debugging symbols',
96                     0),
97         BoolOption ('optimising', 'compile with optimising',
98                     1),
99         BoolOption ('shared', 'build shared libraries',
100                     0),
101         BoolOption ('static', 'build static libraries',
102                     1),
103         BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
104                     1),
105         BoolOption ('verbose', 'run commands with verbose flag',
106                     0),
107         )
108
109 Help (usage + opts.GenerateHelpText (env))
110
111 env = Environment (options = opts)
112
113 opts.Update (env)
114 opts.Save ('config.cache', env)
115
116 env.CacheDir (os.path.join (env['build'], '=build-cache'))
117
118 #ugh
119 sys.path.append (os.path.join ('.', 'stepmake', 'bin'))
120 import packagepython
121 package = packagepython.Package ('.')
122
123 env['version'] = packagepython.version_tuple_to_str (package.version)
124 env['bindir'] = os.path.join (env['prefix'], 'bin')
125 env['sharedir'] = os.path.join (env['prefix'], 'share')
126 env['libdir'] = os.path.join (env['prefix'], 'lib')
127 env['localedir'] = os.path.join (env['sharedir'], 'locale')
128
129 env['sharedir_package'] = os.path.join (env['sharedir'], package.name)
130 env['sharedir_package_version'] = os.path.join (env['sharedir_package'],
131                                                  env['version'])
132 env['lilypondprefix'] = os.path.join (env['sharedir_package_version'])
133
134
135 if env['debugging']:
136         env.Append (CFLAGS = '-g')
137         env.Append (CXXFLAGS = '-g')
138 if env['optimising']:
139         env.Append (CFLAGS = '-O2')
140         env.Append (CXXFLAGS = '-O2')
141         env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
142 if env['warnings']:
143         env.Append (CFLAGS = '-W ')
144         env.Append (CFLAGS = '-Wall')
145         # what about = ['-W', '-Wall', ...]?
146         env.Append (CXXFLAGS = '-W')
147         env.Append (CXXFLAGS = '-Wall')
148         env.Append (CXXFLAGS = '-Wconversion')
149
150
151
152 ##Import ('env')
153 here = os.getcwd ()
154 reldir = str (Dir ('.').srcnode ())
155 os.chdir (reldir)
156 srcdir = os.getcwd ()
157 os.chdir (here)
158 ##outdir = os.path.join (env['build'], reldir, env['out'])
159 outdir = os.path.join (env['build'], env['out'])
160
161 env['srcdir'] = srcdir
162 build = env['build']
163 out = env['out']
164
165
166 def list_sort (lst):
167         sorted = lst
168         sorted.sort ()
169         return sorted
170
171 env['MFMODE'] = 'ljfour'
172 config_h = os.path.join (outdir, 'config.h')
173 env.Alias ('config', config_h)
174
175 def configure (env):
176         conf = Configure (env)
177
178         vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
179         def get_version (program):
180                 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
181                 pipe = os.popen (command)
182                 output = pipe.read ()
183                 if pipe.close ():
184                         return None
185                 v = re.sub (vre, '\\1', output)
186                 return string.split (v, '.')
187
188         def assert_version (lst, program, minimal, description, package):
189                 global required
190                 sys.stdout.write ('Checking %s version... ' % program)
191                 actual = get_version (program)
192                 if not actual:
193                         print 'not found'
194                         lst.append ((description, package, minimal, program,
195                                      'not installed'))
196                         return
197                 sys.stdout.write (string.join (actual, '.'))
198                 sys.stdout.write ('\n')
199                 if actual < string.split (minimal, '.'):
200                         lst.append ((description, package, minimal, program,
201                                      string.join (actual, '.')))
202
203         required = []
204         assert_version (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
205         assert_version (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
206         assert_version (required, 'python', '2.1', 'Python (www.python.org)', 'python')
207         assert_version (required, 'guile-config', '1.6', 'GUILE development',
208                         'libguile-dev or guile-devel')
209         # Do not use bison 1.50 and 1.75.
210         assert_version (required, 'bison', '1.25', 'Bison -- parser generator',
211                         'bison')
212         assert_version (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
213
214
215         optional = []
216         assert_version (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
217         assert_version (optional, 'guile', '1.6', 'GUILE scheme',
218                         'libguile-dev or guile-devel')
219         assert_version (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
220                         'mftrace')
221         assert_version (optional, 'perl', '4.0',
222                         'Perl practical efficient readonly language', 'perl')
223         #assert_version (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
224
225
226         defines = {
227            'DIRSEP' : "'/'",
228            'PATHSEP' : "':'",
229            'TOPLEVEL_VERSION' : '"' + env['version'] + '"',
230            'PACKAGE': '"' + package.name + '"',
231            'DATADIR' : '"' + env['sharedir'] + '"',
232            'LILYPOND_DATADIR' : '"' + env['sharedir_package'] + '"',
233            'LOCAL_LILYPOND_DATADIR' : '"' + env['sharedir_package_version'] + '"',
234            'LOCALEDIR' : '"' + env['localedir'] + '"',
235         }
236
237
238         command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
239         PYTHON_INCLUDE = os.popen (command).read ()
240         env.Append (CPPPATH = PYTHON_INCLUDE)
241
242         headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
243         for i in headers:
244                 if conf.CheckCHeader (i):
245                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
246                         defines[key] = '1'
247
248         ccheaders = ('sstream',)
249         for i in ccheaders:
250                 if conf.CheckCXXHeader (i):
251                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
252                         defines[key] = '1'
253
254         functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
255         for i in functions:
256                 if 0 or conf.CheckFunc (i):
257                         key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
258                         defines[key] = '1'
259
260         key = 'HAVE_FLEXLEXER_YY_CURRENT_BUFFER'
261
262         sys.stdout.write('Checking for yy_current_buffer ... ')
263         sys.stdout.flush()
264         res = conf.TryCompile ("""using namespace std;
265         #include <FlexLexer.h>
266         class yy_flex_lexer: public yyFlexLexer
267         {
268           public:
269             yy_flex_lexer ()
270             {
271               yy_current_buffer = 0;
272             }
273         };""", '.cc')
274         if res:
275                 defines[key] = '1'
276                 sys.stdout.write('yes\n')
277         else:
278                 sys.stdout.write('no\n')
279
280
281         if conf.CheckLib ('dl'):
282                 pass
283
284         if conf.CheckLib ('kpathsea'):
285                 defines['KPATHSEA'] = '1'
286
287         # huh? 
288         if conf.CheckLib ('kpathsea', 'kpse_find_file'):
289                 defines['HAVE_KPSE_FIND_FILE'] = '1'
290         if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
291                 defines['HAVE_KPSE_FIND_TFM'] = '1'
292
293         #this could happen after flower...
294         env.ParseConfig ('guile-config compile')
295
296         #this could happen only for compiling pango-*
297         if env['gui']:
298                 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
299                 env.ParseConfig ('pkg-config --cflags --libs pango')
300                 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
301                         defines['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
302
303                 if conf.CheckLib ('pango-1.0',
304                                   'pango_fc_font_map_add_decoder_find_func'):
305                         defines['HAVE_PANGO_CVS'] = '1'
306                         defines['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
307
308         # use Command
309         if not os.path.exists (outdir):
310                 os.mkdir (outdir)
311
312         config = open (config_h, 'w')
313         for i in list_sort (defines.keys ()):
314                 config.write ('#define %s %s\n' % (i, defines[i]))
315         config.close ()
316
317         os.system (sys.executable \
318                    + ' ./stepmake/bin/make-version.py VERSION > '\
319                    + os.path.join (outdir, 'version.hh'))
320
321         if required:
322                 print
323                 print '********************************'
324                 print 'Please install required packages'
325                 for i in required:
326                         print '%s:      %s-%s or newer (found: %s %s)' % i
327                 sys.exit (1)
328
329         if optional:
330                 print
331                 print '*************************************'
332                 print 'Consider installing optional packages'
333                 for i in optional:
334                         print '%s:      %s-%s or newer (found: %s %s)' % i
335
336         return conf.Finish ()
337
338 # Hmm.  Must configure when building lily, to get compiler and linker
339 # flags set-up.
340 # FIXME
341 if 1 or not os.path.exists (config_h) or 'config' in COMMAND_LINE_TARGETS:
342         env = configure (env)
343
344 env.Append (LIBPATH = ['#/flower/' + out,], 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 sourcetree build (--srcdir build)
386         # and ./out build.
387         if (build and build != '.') \
388            or (out and out != '.'):
389                 env.BuildDir (b, d, duplicate=0)
390         SConscript (os.path.join (b, 'SConscript'))
391
392 # as a builder?
393 def symlink_tree (prefix):
394         def mkdirs (dir):
395                 def mkdir (dir):
396                         if not dir:
397                                 os.chdir (os.sep)
398                                 return
399                         if not os.path.isdir (dir):
400                                 if os.path.exists (dir):
401                                         os.unlink (dir)
402                                 os.mkdir (dir)
403                         os.chdir (dir)
404                 map (mkdir, string.split (dir, os.sep))
405         def symlink (src, dst):
406                 os.chdir (absbuild)
407                 dir = os.path.dirname (dst)
408                 mkdirs (dir)
409                 if src[0] == '#':
410                         frm = os.path.join (srcdir, src[1:])
411                 else:
412                         depth = len (string.split (dir, '/'))
413                         frm = os.path.join ('../' * depth, src, out)
414                 os.symlink (frm, os.path.basename (dst))
415         map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
416              (('python', 'lib/lilypond/python'),
417               # UGHR, lilypond.py uses lilypond-bin from PATH
418               ('lily',   'bin'),
419               ('#mf',    'share/lilypond/fonts/mf'),
420               ('mf',     'share/lilypond/fonts/afm'),
421               ('mf',     'share/lilypond/fonts/tfm'),
422               ('mf',     'share/lilypond/fonts/type1'),
423               ('#tex',   'share/lilypond/tex/source'),
424               ('mf',     'share/lilypond/tex/generate'),
425               ('#ly',    'share/lilypond/ly'),
426               ('#scm',   'share/lilypond/scm'),
427               ('#ps',    'share/lilypond/ps'),
428               ('elisp',  'share/lilypond/elisp')))
429         os.chdir (srcdir)
430
431 if env['debugging']:
432         prefix = os.path.join (out, 'usr')
433         if not os.path.exists (os.path.join (absbuild, prefix)):
434                 symlink_tree (prefix)
435
436 #### dist, tar
437 def cvs_files (dir):
438         entries = open (os.path.join (dir, 'CVS/Entries')).readlines ()
439         files = filter (lambda x: x[0] != 'D', entries)
440         return map (lambda x: os.path.join (dir, x[1:x[1:].index ('/')+1]),
441                     files)
442
443 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
444 foo = map (lambda x: env.Texi2txt (x + '.txt',
445                                    os.path.join ('Documentation/topdocs',
446                                                  x)),
447            readme_files)
448 txt_files = map (lambda x: x + '.txt', readme_files)
449 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
450 tar_base = package.name + '-' + env['version']
451 tar_name = tar_base + '.tar.gz'
452 ball_prefix = os.path.join (outdir, tar_base)
453 tar_ball = os.path.join (outdir, tar_name)
454
455 dist_files = src_files + txt_files
456 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
457 map (lambda x: env.Depends (tar_ball, x), ball_files)
458 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
459                             'ln $SOURCE $TARGET'), dist_files)
460 tar = env.Command (tar_ball, src_files,
461                    'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
462 env.Alias ('tar', tar)
463
464 dist_ball = os.path.join (package.release_dir, tar_name)
465 env.Command (dist_ball, tar_ball,
466              'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
467              + 'ln $SOURCE $TARGET')
468 env.Depends ('dist', dist_ball)
469 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
470 patch = env.PATCH (patch_name, tar_ball)
471 env.Depends (patch_name, dist_ball)
472 env.Alias ('release', patch)
473