4 Experimental scons (www.scons.org) building.
10 build from source directory ./TARGET (not recursive)
14 scons [config] # configure
19 run=$(pwd)/out-scons/usr
20 export LOCALE=$run/share/locale
21 export TEXMF='{'$run/share/lilypond,$(kpsexpand '$TEXMF')'}'
24 #optionally, if you do not use custom.py below
25 #export LILYPONDPREFIX=$run/share/lilypond
27 lilypond-bin input/simple
30 scons mf-essential # build minimal mf stuff
32 scons doc # build web doc
33 scons config # reconfigure
34 scons install # install
38 scons / # build *everything* (including installation)
40 Options (see scons -h)
41 scons build=DIR # clean scrdir build, output below DIR
42 scons out=DIR # write output for alterative config to DIR
51 os.path.join (os.getcwd (), '=install')
52 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
61 # * more program configure tests (mfont, ...?)
63 # * more fine-grained config.hh -- move lilypondprefix to version.hh?
64 # - config.hh: changes after system upgrades, affects all files
65 # - version.hh: prefix, version etc? affects few
67 # * grep FIXME $(find . -name 'S*t')
79 EnsureSConsVersion (0, 95)
81 # SConscripts are only needed in directories where something needs
82 # to be done, building or installing
83 subdirs = ['flower', 'lily', 'mf', 'scm', 'ly',
84 'scripts', 'elisp', 'buildscripts', 'po',
85 'Documentation', 'Documentation/user', 'Documentation/topdocs',
86 'Documentation/bibliography',
87 'input', 'input/regression', 'input/test', 'input/template',
93 scons [KEY=VALUE].. [TARGET|DIR]..
95 TARGETS: clean, config, doc, dist, install, mf-essential, po-update,
96 realclean, release, tar, TAGS
101 config_cache = 'config.cache'
116 # Put your favourite stuff in custom.py
117 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
118 opts.Add ('prefix', 'Install prefix', '/usr/')
119 opts.Add ('out', 'Output directory', 'out-scons')
120 opts.Add ('build', 'Build directory', '.')
122 BoolOption ('warnings', 'compile with -Wall and similiar',
124 BoolOption ('debugging', 'compile with debugging symbols',
126 BoolOption ('optimising', 'compile with optimising',
128 BoolOption ('shared', 'build shared libraries',
130 BoolOption ('static', 'build static libraries',
132 BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
134 BoolOption ('verbose', 'run commands with verbose flag',
136 BoolOption ('checksums', 'use checksums instead of timestamps',
140 srcdir = Dir ('.').srcnode ().abspath
143 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
145 package = packagepython.Package (srcdir)
146 version = packagepython.version_tuple_to_str (package.version)
148 ENV = { 'PATH' : os.environ['PATH'] }
149 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
150 if os.environ.has_key (key):
151 ENV[key] = os.environ[key]
157 MAKEINFO = 'LANG= makeinfo',
158 PERL = '/usr/bin/perl',
159 PYTHON = '/usr/bin/python',
162 ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
163 LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
164 LILYPOND_BOOK_FLAGS = '',
165 LILYPOND_BOOK_FORMAT = 'texi-html',
166 LILYPOND_PY = srcdir + '/scripts/lilypond.py',
167 MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
169 PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
170 'usr/pkg/gnome/lib'),
171 os.path.join (os.environ['HOME'],
172 'usr/pkg/pango/lib')],
174 TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
175 TOPLEVEL_VERSION = version,
178 Help (usage + opts.GenerateHelpText (env))
180 # Add all config_vars to opts, so that they will be read and saved
181 # together with the other configure options.
182 map (lambda x: opts.AddOptions ((x,)), config_vars)
187 SetOption ('max_drift', 0)
189 absbuild = Dir (env['build']).abspath
190 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
191 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
193 CacheDir (os.path.join (outdir, 'build-cache'))
196 # No need to set $LILYPONDPREFIX to run lily, but cannot install...
197 env['prefix'] = run_prefix
199 prefix = env['prefix']
200 bindir = os.path.join (prefix, 'bin')
201 sharedir = os.path.join (prefix, 'share')
202 libdir = os.path.join (prefix, 'lib')
203 localedir = os.path.join (sharedir, 'locale')
204 sharedir_package = os.path.join (sharedir, package.name)
205 sharedir_package_version = os.path.join (sharedir_package, version)
206 lilypondprefix = sharedir_package_version
208 # post-option environment-update
214 lilypond_datadir = sharedir_package,
215 localedir = localedir,
216 local_lilypond_datadir = sharedir_package_version,
217 lilypondprefix = lilypondprefix,
218 sharedir_package = sharedir_package,
219 sharedir_package_version = sharedir_package_version,
223 env.Append (CFLAGS = '-g')
224 env.Append (CXXFLAGS = '-g')
225 if env['optimising']:
226 env.Append (CFLAGS = '-O2')
227 env.Append (CXXFLAGS = ['-O2', '-DSTRING_UTILS_INLINED'])
229 env.Append (CFLAGS = ['-W', '-Wall'])
230 # CXXFLAGS = $CFLAGS ...
231 env.Append (CXXFLAGS = ['-W', '-Wall', '-Wconversion'])
232 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
234 env['__verbose'] = ' --verbose'
235 env['set__x'] = 'set -x;'
237 config_hh = os.path.join (outdir, 'config.hh')
238 version_hh = os.path.join (outdir, 'version.hh')
240 env.Alias ('config', config_cache)
243 ## Explicit target and dependencies
245 if 'clean' in COMMAND_LINE_TARGETS:
246 # ugh: prevent reconfigure instead of clean
247 os.system ('touch %s' % config_cache)
249 command = sys.argv[0] + ' -c .'
250 sys.stdout.write ('Running %s ... ' % command)
251 sys.stdout.write ('\n')
252 s = os.system (command)
253 if os.path.exists (config_cache):
254 os.unlink (config_cache)
257 if 'realclean' in COMMAND_LINE_TARGETS:
258 command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
259 sys.stdout.write ('Running %s ... ' % command)
260 sys.stdout.write ('\n')
261 s = os.system (command)
262 if os.path.exists (config_cache):
263 os.unlink (config_cache)
266 # Without target arguments, build lily only
267 if not COMMAND_LINE_TARGETS:
269 env.Alias ('all', '.')
272 'Documentation/user',
273 'Documentation/topdocs'])
275 env.Depends ('doc', ['lily', 'mf'])
276 env.Depends ('input', ['lily', 'mf'])
284 def configure (target, source, env):
285 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
286 def get_version (program):
287 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
288 pipe = os.popen (command)
289 output = pipe.read ()
292 v = re.sub (vre, '\\1', output)
293 return string.split (v, '.')
295 def test_program (lst, program, minimal, description, package):
296 sys.stdout.write ('Checking %s version... ' % program)
297 actual = get_version (program)
300 lst.append ((description, package, minimal, program,
303 sys.stdout.write (string.join (actual, '.'))
304 sys.stdout.write ('\n')
305 if actual < string.split (minimal, '.'):
306 lst.append ((description, package, minimal, program,
307 string.join (actual, '.')))
310 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
311 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
312 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
313 test_program (required, 'guile-config', '1.6', 'GUILE development',
314 'libguile-dev or guile-devel')
315 # Do not use bison 1.50 and 1.75.
316 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
318 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
322 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
323 test_program (optional, 'guile', '1.6', 'GUILE scheme',
324 'libguile-dev or guile-devel')
325 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
327 test_program (optional, 'perl', '4.0',
328 'Perl practical efficient readonly language', 'perl')
329 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
331 def CheckYYCurrentBuffer (context):
332 context.Message ('Checking for yy_current_buffer... ')
333 ret = conf.TryCompile ("""using namespace std;
334 #include <FlexLexer.h>
335 class yy_flex_lexer: public yyFlexLexer
340 yy_current_buffer = 0;
346 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
347 : CheckYYCurrentBuffer })
352 'TOPLEVEL_VERSION' : '"' + version + '"',
353 'PACKAGE': '"' + package.name + '"',
354 'DATADIR' : '"' + sharedir + '"',
355 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
356 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
357 'LOCALEDIR' : '"' + localedir + '"',
359 conf.env.Append (DEFINES = defines)
361 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
362 PYTHON_INCLUDE = os.popen (command).read ()
363 env.Append (CPPPATH = PYTHON_INCLUDE)
365 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
367 if conf.CheckCHeader (i):
368 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
369 conf.env['DEFINES'][key] = 1
371 ccheaders = ('sstream',)
373 if conf.CheckCXXHeader (i):
374 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
375 conf.env['DEFINES'][key] = 1
377 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
379 if 0 or conf.CheckFunc (i):
380 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
381 conf.env['DEFINES'][key] = 1
383 if conf.CheckYYCurrentBuffer ():
384 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
386 if conf.CheckLib ('dl'):
389 if conf.CheckLib ('kpathsea'):
390 conf.env['DEFINES']['KPATHSEA'] = 1
393 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
394 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
395 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
396 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
398 #this could happen after flower...
399 env.ParseConfig ('guile-config compile')
401 #this could happen only for compiling pango-*
403 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
404 env.ParseConfig ('pkg-config --cflags --libs pango')
405 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
406 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
408 if conf.CheckLib ('pango-1.0',
409 'pango_fc_font_map_add_decoder_find_func'):
410 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
411 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
415 print '********************************'
416 print 'Please install required packages'
418 print '%s: %s-%s or newer (found: %s %s)' % i
423 print '*************************************'
424 print 'Consider installing optional packages'
426 print '%s: %s-%s or newer (found: %s %s)' % i
428 return conf.Finish ()
430 def config_header (target, source, env):
431 config = open (str (target[0]), 'w')
432 for i in list_sort (env['DEFINES'].keys ()):
433 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
435 env.Command (config_hh, config_cache, config_header)
451 if not d.has_key (lst[i]):
460 def save_config_cache (env):
461 ## FIXME: Is this smart, using option cache for saving
462 ## config.cache? I cannot seem to find the official method.
463 for i in config_vars:
464 if env.has_key (i) and type (env[i]) == type ([]):
465 env[i] = uniquify (env[i])
466 opts.Save (config_cache, env)
468 ## FIXME: Something changes in the ENVironment that triggers
469 ## rebuild of everything if we continue after configuring.
470 ## Maybe there's a variable missing from config_vars?
472 ## How to print and debug the reasons scons has for rebuilding
473 ## a target (make --debug)?
475 ## heet van de naald:
476 ## - Add a --debug=explain option that reports the reason(s) why SCons
478 if 'config' in COMMAND_LINE_TARGETS:
479 sys.stdout.write ('\n')
480 sys.stdout.write ('LilyPond configured')
481 sys.stdout.write ('\n')
482 sys.stdout.write ('now run')
483 sys.stdout.write ('\n')
484 sys.stdout.write (' scons [TARGET|DIR]...')
485 sys.stdout.write ('\n')
487 elif 1: #not env['checksums']:
488 command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS)
489 sys.stdout.write ('Running %s ... ' % command)
490 sys.stdout.write ('\n')
491 s = os.system (command)
495 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
496 os.unlink (config_cache)
498 # scons: *** Calling Configure from Builders is not supported.
499 # env.Command (config_cache, None, configure)
500 if not os.path.exists (config_cache) \
501 or (os.stat ('SConstruct')[stat.ST_MTIME]
502 > os.stat (config_cache)[stat.ST_MTIME]):
503 env = configure (None, None, env)
504 save_config_cache (env)
506 env.Command (version_hh, '#/VERSION',
507 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
509 # post-config environment update
512 run_prefix = run_prefix,
513 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
515 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
516 CPPPATH = [outdir, '#',],
517 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
519 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
520 '#/input/test', '#/input/tutorial',
521 os.path.join (absbuild, 'mf', env['out']),
522 '#/Documentation/user',
523 os.path.join (absbuild, 'Documentation',
525 os.path.join (absbuild, 'Documentation/user',
528 MAKEINFO_PATH = ['.', '#/Documentation/user',
529 os.path.join (absbuild, 'Documentation/user',
534 SConscript ('buildscripts/builder.py')
537 def symlink_tree (target, source, env):
543 if not os.path.isdir (dir):
544 if os.path.exists (dir):
548 map (mkdir, string.split (dir, os.sep))
549 def symlink (src, dst):
551 dir = os.path.dirname (dst)
554 frm = os.path.join (srcdir, src[1:])
556 depth = len (string.split (dir, '/'))
557 if src.find ('@') > -1:
558 frm = os.path.join ('../' * depth,
559 string.replace (src, '@',
562 frm = os.path.join ('../' * depth, src,
565 frm = os.path.join (frm, os.path.basename (dst))
567 print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
568 os.symlink (frm, os.path.basename (dst))
569 shutil.rmtree (run_prefix)
570 prefix = os.path.join (env['out'], 'usr')
571 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
574 # /$ := add dst file_name
575 (('python', 'lib/lilypond/python'),
576 ('lily/', 'bin/lilypond-bin'),
577 ('scripts/', 'bin/lilypond'),
578 ('scripts/', 'bin/lilypond-book'),
579 ('#mf', 'share/lilypond/fonts/mf'),
580 ('mf', 'share/lilypond/fonts/afm'),
581 ('mf', 'share/lilypond/fonts/tfm'),
582 ('mf', 'share/lilypond/fonts/type1'),
583 ('#tex', 'share/lilypond/tex/source'),
584 ('mf', 'share/lilypond/tex/generate'),
585 ('#ly', 'share/lilypond/ly'),
586 ('#scm', 'share/lilypond/scm'),
587 ('#ps', 'share/lilypond/ps'),
588 ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
589 ('elisp', 'share/lilypond/elisp')))
593 stamp = os.path.join (run_prefix, 'stamp')
594 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
595 env.Depends ('lily', stamp)
601 def cvs_entry_is_dir (line):
602 return line[0] == 'D' and line[-2] == '/'
604 def cvs_entry_is_file (line):
605 return line[0] == '/' and line[-2] == '/'
608 ENTRIES = os.path.join (dir, 'CVS/Entries')
609 if not os.path.exists (ENTRIES):
611 entries = open (ENTRIES).readlines ()
612 dir_entries = filter (cvs_entry_is_dir, entries)
613 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
615 return dirs + map (cvs_dirs, dirs)
618 ENTRIES = os.path.join (dir, 'CVS/Entries')
619 entries = open (ENTRIES).readlines ()
620 file_entries = filter (cvs_entry_is_file, entries)
621 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
622 return map (lambda x: os.path.join (dir, x), files)
624 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
626 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
627 foo = map (lambda x: env.TXT (x + '.txt',
628 os.path.join ('Documentation/topdocs', x)),
630 txt_files = map (lambda x: x + '.txt', readme_files)
631 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
632 tar_base = package.name + '-' + version
633 tar_name = tar_base + '.tar.gz'
634 ball_prefix = os.path.join (outdir, tar_base)
635 tar_ball = os.path.join (outdir, tar_name)
637 dist_files = src_files + txt_files
638 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
639 map (lambda x: env.Depends (tar_ball, x), ball_files)
640 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
641 'ln $SOURCE $TARGET'), dist_files)
642 tar = env.Command (tar_ball, src_files,
643 'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
644 env.Alias ('tar', tar)
646 dist_ball = os.path.join (package.release_dir, tar_name)
647 env.Command (dist_ball, tar_ball,
648 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
649 + 'ln $SOURCE $TARGET')
650 env.Depends ('dist', dist_ball)
651 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
652 patch = env.PATCH (patch_name, tar_ball)
653 env.Depends (patch_name, dist_ball)
654 env.Alias ('release', patch)
657 ETAGSFLAGS = ["""--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\1/'""",
658 """--regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\1/'"""])
659 # filter-out some files?
660 env.Command ('TAGS', src_files, 'etags $ETAGSFLAGS $SOURCES')
663 if os.path.exists (os.path.join (d, 'SConscript')):
664 b = os.path.join (env['build'], d, env['out'])
665 # Support clean sourcetree build (--srcdir build)
667 if os.path.abspath (b) != os.path.abspath (d):
668 env.BuildDir (b, d, duplicate = 0)
669 SConscript (os.path.join (b, 'SConscript'))