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