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
56 os.path.join (os.getcwd (), '=install')
57 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
65 # * more program configure tests (mfont, ...?)
67 # * split doc target: doc input examples mutopia?
69 # * more fine-grained config.hh -- move lilypondprefix to version.hh?
70 # - config.hh: changes after system upgrades, affects all files
71 # - version.hh: prefix, version etc? affects few
73 # - what about GUILE_*_VERSION, seems to be the major culprit,
74 # for config.hh dependency escalation. Is the lily-guile.hh
75 # workaround necessary at all for GUILE > 1.5?
77 # * grep FIXME $(find . -name 'S*t')
89 EnsureSConsVersion (0, 95)
92 scons [KEY=VALUE].. [TARGET|DIR]..
94 TARGETS: clean, config, doc, dist, install, mf-essential, po-update,
95 realclean, release, tar, TAGS
100 config_cache = 'scons.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 PERL = '/usr/bin/perl',
158 PYTHON = '/usr/bin/python',
161 MAKEINFO = 'LANG= makeinfo',
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')],
175 TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
176 TOPLEVEL_VERSION = version,
179 # Add all config_vars to opts, so that they will be read and saved
180 # together with the other configure options.
181 map (lambda x: opts.AddOptions ((x,)), config_vars)
183 Help (usage + opts.GenerateHelpText (env))
187 # Using content checksums prevents rebuilds after [re]configure if
188 # config.hh has not changed.
190 SetOption ('max_drift', 0)
191 TargetSignatures ("content")
193 absbuild = Dir (env['build']).abspath
194 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
195 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
197 CacheDir (os.path.join (outdir, 'build-cache'))
200 # No need to set $LILYPONDPREFIX to run lily, but cannot install...
201 env['prefix'] = run_prefix
203 prefix = env['prefix']
204 bindir = os.path.join (prefix, 'bin')
205 sharedir = os.path.join (prefix, 'share')
206 libdir = os.path.join (prefix, 'lib')
207 localedir = os.path.join (sharedir, 'locale')
208 sharedir_package = os.path.join (sharedir, package.name)
209 sharedir_package_version = os.path.join (sharedir_package, version)
210 lilypondprefix = sharedir_package_version
212 # post-option environment-update
218 lilypond_datadir = sharedir_package,
219 localedir = localedir,
220 local_lilypond_datadir = sharedir_package_version,
221 lilypondprefix = lilypondprefix,
222 sharedir_package = sharedir_package,
223 sharedir_package_version = sharedir_package_version,
227 env.Append (CCFLAGS = ['-g', '-pipe'])
228 if env['optimising']:
229 env.Append (CCFLAGS = '-O2')
230 env.Append (CXXFLAGS = ['-DSTRING_UTILS_INLINED'])
232 env.Append (CCFLAGS = ['-W', '-Wall'])
233 env.Append (CXXFLAGS = ['-Wconversion'])
236 env.Append (LINKFLAGS = ['-Wl,--export-dynamic'])
239 env['__verbose'] = ' --verbose'
240 env['set__x'] = 'set -x;'
242 config_hh = os.path.join (outdir, 'config.hh')
243 version_hh = os.path.join (outdir, 'version.hh')
245 env.Alias ('config', config_cache)
248 ## Explicit target and dependencies
250 if 'clean' in COMMAND_LINE_TARGETS:
251 # ugh: prevent reconfigure instead of clean
252 os.system ('touch %s' % config_cache)
254 command = sys.argv[0] + ' -c .'
255 sys.stdout.write ('Running %s ... ' % command)
256 sys.stdout.write ('\n')
257 s = os.system (command)
258 if os.path.exists (config_cache):
259 os.unlink (config_cache)
262 if 'realclean' in COMMAND_LINE_TARGETS:
263 command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")'
264 sys.stdout.write ('Running %s ... ' % command)
265 sys.stdout.write ('\n')
266 s = os.system (command)
267 if os.path.exists (config_cache):
268 os.unlink (config_cache)
271 # Declare SConscript phonies
272 env.Alias ('minimal', config_cache)
273 env.Alias ('mf-essential', config_cache)
275 env.Alias ('minimal', ['lily', 'mf-essential'])
276 env.Alias ('all', ['minimal', 'mf', '.'])
277 # Do we want the doc/web separation?
280 'Documentation/user',
281 'Documentation/topdocs',
282 'Documentation/bibliography',
285 # Without target arguments, do minimal build
286 if not COMMAND_LINE_TARGETS:
287 env.Default (['minimal'])
289 # GNU Make rerouting compat:
290 env.Alias ('web', 'doc')
297 def configure (target, source, env):
298 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
299 def get_version (program):
300 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
301 pipe = os.popen (command)
302 output = pipe.read ()
305 v = re.sub (vre, '\\1', output)
306 return string.split (v, '.')
308 def test_program (lst, program, minimal, description, package):
309 sys.stdout.write ('Checking %s version... ' % program)
310 actual = get_version (program)
313 lst.append ((description, package, minimal, program,
316 sys.stdout.write (string.join (actual, '.'))
317 sys.stdout.write ('\n')
318 if actual < string.split (minimal, '.'):
319 lst.append ((description, package, minimal, program,
320 string.join (actual, '.')))
322 for i in ['bash', 'perl', 'python', 'sh']:
323 sys.stdout.write ('Checking for %s... ' % i)
325 key = string.upper (i)
330 sys.stdout.write ('not found: %s (using: %s)' \
333 sys.stdout.write ('\n')
336 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
337 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
338 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
339 test_program (required, 'guile-config', '1.6', 'GUILE development',
340 'libguile-dev or guile-devel')
341 # Do not use bison 1.50 and 1.75.
342 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
344 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
348 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
349 test_program (optional, 'guile', '1.6', 'GUILE scheme',
350 'libguile-dev or guile-devel')
351 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
353 test_program (optional, 'perl', '4.0',
354 'Perl practical efficient readonly language', 'perl')
355 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
357 def CheckYYCurrentBuffer (context):
358 context.Message ('Checking for yy_current_buffer... ')
359 ret = conf.TryCompile ("""using namespace std;
360 #include <FlexLexer.h>
361 class yy_flex_lexer: public yyFlexLexer
366 yy_current_buffer = 0;
372 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
373 : CheckYYCurrentBuffer })
376 'DIRSEP' : "'%s'" % os.sep,
377 'PATHSEP' : "'%s'" % os.pathsep,
378 'TOPLEVEL_VERSION' : '"' + version + '"',
379 'PACKAGE': '"' + package.name + '"',
380 'DATADIR' : '"' + sharedir + '"',
381 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
382 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
383 'LOCALEDIR' : '"' + localedir + '"',
385 conf.env.Append (DEFINES = defines)
387 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
388 PYTHON_INCLUDE = os.popen (command).read ()
389 env.Append (CPPPATH = PYTHON_INCLUDE)
391 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
393 if conf.CheckCHeader (i):
394 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
395 conf.env['DEFINES'][key] = 1
397 ccheaders = ('sstream',)
399 if conf.CheckCXXHeader (i):
400 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
401 conf.env['DEFINES'][key] = 1
403 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
405 if 0 or conf.CheckFunc (i):
406 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
407 conf.env['DEFINES'][key] = 1
409 if conf.CheckYYCurrentBuffer ():
410 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
412 if conf.CheckLib ('dl'):
415 if conf.CheckLib ('kpathsea'):
416 conf.env['DEFINES']['KPATHSEA'] = 1
419 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
420 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
421 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
422 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
424 #this could happen after flower...
425 env.ParseConfig ('guile-config compile')
427 #this could happen only for compiling pango-*
429 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
430 env.ParseConfig ('pkg-config --cflags --libs pango')
431 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
432 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
434 if conf.CheckLib ('pango-1.0',
435 'pango_fc_font_map_add_decoder_find_func'):
436 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
437 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
441 print '********************************'
442 print 'Please install required packages'
444 print '%s: %s-%s or newer (found: %s %s)' % i
449 print '*************************************'
450 print 'Consider installing optional packages'
452 print '%s: %s-%s or newer (found: %s %s)' % i
454 return conf.Finish ()
456 def config_header (target, source, env):
457 config = open (str (target[0]), 'w')
458 for i in list_sort (env['DEFINES'].keys ()):
459 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
461 env.Command (config_hh, config_cache, config_header)
477 if not d.has_key (lst[i]):
485 def uniquify_config_vars (env):
486 for i in config_vars:
487 if env.has_key (i) and type (env[i]) == type ([]):
488 env[i] = uniquify (env[i])
490 def save_config_cache (env):
491 ## FIXME: Is this smart, using option cache for saving
492 ## config.cache? I cannot seem to find the official method.
493 uniquify_config_vars (env)
494 opts.Save (config_cache, env)
496 if 'config' in COMMAND_LINE_TARGETS:
497 sys.stdout.write ('\n')
498 sys.stdout.write ('LilyPond configured')
499 sys.stdout.write ('\n')
500 sys.stdout.write ('now run')
501 sys.stdout.write ('\n')
502 sys.stdout.write (' scons [TARGET|DIR]...')
503 sys.stdout.write ('\n')
505 elif not env['checksums']:
506 # When using timestams, config.hh is NEW. The next
507 # build triggers recompilation of everything. Exiting
508 # here makes SCons use the actual timestamp for config.hh
509 # and prevents recompiling everything the next run.
510 command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS)
511 sys.stdout.write ('Running %s ... ' % command)
512 sys.stdout.write ('\n')
513 s = os.system (command)
517 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
518 os.unlink (config_cache)
520 # scons: *** Calling Configure from Builders is not supported.
521 # env.Command (config_cache, None, configure)
522 if not os.path.exists (config_cache) \
523 or (os.stat ('SConstruct')[stat.ST_MTIME]
524 > os.stat (config_cache)[stat.ST_MTIME]):
525 env = configure (None, None, env)
526 save_config_cache (env)
527 elif env['checksums']:
528 # just save everything
529 save_config_cache (env)
531 env.Command (version_hh, '#/VERSION',
532 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
534 # post-config environment update
537 run_prefix = run_prefix,
538 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
540 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
541 ##CPPPATH = [outdir, '#',], # do not read auto*'s header
542 CPPPATH = [outdir, ],
543 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
545 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
546 '#/input/test', '#/input/tutorial',
547 os.path.join (absbuild, 'mf', env['out']),
548 '#/Documentation/user',
549 os.path.join (absbuild, 'Documentation',
551 os.path.join (absbuild, 'Documentation/user',
554 MAKEINFO_PATH = ['.', '#/Documentation/user',
555 os.path.join (absbuild, 'Documentation/user',
560 SConscript ('buildscripts/builder.py')
563 def symlink_tree (target, source, env):
569 if not os.path.isdir (dir):
570 if os.path.exists (dir):
574 map (mkdir, string.split (dir, os.sep))
575 def symlink (src, dst):
577 dir = os.path.dirname (dst)
580 frm = os.path.join (srcdir, src[1:])
582 depth = len (string.split (dir, '/'))
583 if src.find ('@') > -1:
584 frm = os.path.join ('../' * depth,
585 string.replace (src, '@',
588 frm = os.path.join ('../' * depth, src,
591 frm = os.path.join (frm, os.path.basename (dst))
593 print 'ln -s %s -> %s' % (frm, os.path.basename (dst))
594 os.symlink (frm, os.path.basename (dst))
595 shutil.rmtree (run_prefix)
596 prefix = os.path.join (env['out'], 'usr')
597 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
600 # /$ := add dst file_name
601 (('python', 'lib/lilypond/python'),
602 ('lily/', 'bin/lilypond-bin'),
603 ('scripts/', 'bin/lilypond'),
604 ('scripts/', 'bin/lilypond-book'),
605 ('mf', 'share/lilypond/dvips'),
606 ('#ps', 'share/lilypond/tex/music-drawing-routines.ps'),
607 ('mf', 'share/lilypond/afm'),
608 ('mf', 'share/lilypond/tfm'),
609 ('#mf', 'share/lilypond/fonts/mf'),
610 ('mf', 'share/lilypond/fonts/afm'),
611 ('mf', 'share/lilypond/fonts/tfm'),
612 ('mf', 'share/lilypond/fonts/type1'),
613 ('#tex', 'share/lilypond/tex/source'),
614 ('mf', 'share/lilypond/tex/generate'),
615 ('#ly', 'share/lilypond/ly'),
616 ('#scm', 'share/lilypond/scm'),
617 ('#ps', 'share/lilypond/ps'),
618 ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'),
619 ('elisp', 'share/lilypond/elisp')))
623 stamp = os.path.join (run_prefix, 'stamp')
624 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
625 env.Depends ('lily', stamp)
631 def cvs_entry_is_dir (line):
632 return line[0] == 'D' and line[-2] == '/'
634 def cvs_entry_is_file (line):
635 return line[0] == '/' and line[-2] == '/'
638 ENTRIES = os.path.join (dir, 'CVS/Entries')
639 if not os.path.exists (ENTRIES):
641 entries = open (ENTRIES).readlines ()
642 dir_entries = filter (cvs_entry_is_dir, entries)
643 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
645 return dirs + map (cvs_dirs, dirs)
648 ENTRIES = os.path.join (dir, 'CVS/Entries')
649 entries = open (ENTRIES).readlines ()
650 file_entries = filter (cvs_entry_is_file, entries)
651 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
652 return map (lambda x: os.path.join (dir, x), files)
654 def flatten (tree, lst):
655 if type (tree) == type ([]):
657 if type (i) == type ([]):
663 subdirs = flatten (cvs_dirs ('.'), [])
664 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
665 foo = map (lambda x: env.TXT (x + '.txt',
666 os.path.join ('Documentation/topdocs', x)),
668 txt_files = map (lambda x: x + '.txt', readme_files)
669 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
670 tar_base = package.name + '-' + version
671 tar_name = tar_base + '.tar.gz'
672 ball_prefix = os.path.join (outdir, tar_base)
673 tar_ball = os.path.join (outdir, tar_name)
675 dist_files = src_files + txt_files
676 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
677 map (lambda x: env.Depends (tar_ball, x), ball_files)
678 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
679 'ln $SOURCE $TARGET'), dist_files)
680 tar = env.Command (tar_ball, src_files,
681 ['rm -f $$(find $TARGET.dir -name .sconsign)',
682 'tar czf $TARGET -C $TARGET.dir %s' % tar_base,])
683 env.Alias ('tar', tar)
685 dist_ball = os.path.join (package.release_dir, tar_name)
686 env.Command (dist_ball, tar_ball,
687 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
688 + 'ln $SOURCE $TARGET')
689 env.Depends ('dist', dist_ball)
690 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
691 patch = env.PATCH (patch_name, tar_ball)
692 env.Depends (patch_name, dist_ball)
693 env.Alias ('release', patch)
696 web_base = os.path.join (outdir, 'web')
697 web_ball = os.path.join (web_base, '.tar.gz')
698 env['footify'] = 'MAILADDRESS=bug-lilypond@gnu.org $PYTHON stepmake/bin/add-html-footer.py --name=lilypond --version=$TOPLEVEL_VERSION'
699 web_ext = ['.html', '.ly', '.midi', '.pdf', '.png', '.ps.gz', '.txt',]
700 web_path = '-path "*/$out/*"' + string.join (web_ext, ' -or -path "*/$out/*"')
701 env['web_path'] = web_path
703 # compatible make heritits
704 # fixme: generate in $outdir is cwd/builddir
705 env.Command (web_ball, 'doc',
706 ['$PYTHON buildscripts/mutopia-index.py -o examples.html ./',
707 'cd $absbuild && $footify $$(find . -name "*.html" -print)',
709 'cd $absbuild && rm -f $$(find . -name "*.html~" -print)',
710 'cd $absbuild && find Documentation input $web_path \
712 '''echo '<META HTTP-EQUIV="refresh" content="0;URL=Documentation/out-www/index.html">' > $absbuild/index.html''',
713 '''echo '<html><body>Redirecting to the documentation index...</body></html>' >> $absbuild/index.html''',
714 # UGHR? all .html cruft in cwd goes into the web ball?
715 'cd $absbuild && ls *.html >> $out/weblist',
716 'cat $out/weblist | (cd $absbuild; \
717 GZIP=-9v tar -czf ${TARGET.file} -T -)',])
718 env.Alias ('web', web_ball)
722 ETAGSFLAGS = ["""--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\1/'""",
723 """--regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\1/'"""])
724 # filter-out some files?
725 env.Command ('TAGS', src_files, 'etags $ETAGSFLAGS $SOURCES')
728 # Note: SConscripts are only needed in directories where something needs
729 # to be done, building or installing
731 if os.path.exists (os.path.join (d, 'SConscript')):
732 b = os.path.join (env['build'], d, env['out'])
733 # Support clean sourcetree build (--srcdir build)
735 if os.path.abspath (b) != os.path.abspath (d):
736 env.BuildDir (b, d, duplicate = 0)
737 SConscript (os.path.join (b, 'SConscript'))