4 Experimental scons (www.scons.org) building:
6 scons TARGET builds from source directory ./TARGET (not recursive)
11 scons lily # build lily
13 LILYPONDPREFIX=out-scons/usr/share/lilypond lily/out-scons/lilypond-bin
14 scons doc # build web doc
16 ? scons fonts # build all font stuff (split this? )
18 scons config # reconfigure
20 scons / # builds all possible targets
26 scons build=DIR # scrdir build, write to new tree =build
27 scons out=DIR # write output to deeper dir DIR
29 Optionally, make a custom.py. I have
36 os.path.join (os.getcwd (), '=install')
37 prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond')
46 # * separate environments?
47 # - compile environment checks headers and libraries
48 # - doc environment checks doc stuff
50 # * commandline targets:
52 # * more fine-grained config.h -- move lilypondprefix to version.hh?
53 # - config.h: changes after system upgrades, affects all files
54 # - version.hh: prefix, version etc? affects few
64 # faster but scary: when changing #includes, do scons --implicit-deps-changed
65 # SetOption ('implicit_cache', 1)
67 # SConscripts are only needed in directories where something needs
68 # to be done, building or installing
69 # TODO: Documentation/*, input/*/*, vim, po
70 # rename Documentation/* to ./doc?
72 # somethin's broken wrt config.h checksums?
75 subdirs = ['flower', 'lily', 'mf', 'scm', 'ly', 'Documentation',
76 'Documentation/user', 'Documentation/topdocs',
77 'input', 'scripts', 'elisp',
78 'buildscripts', 'cygwin', 'debian']
81 scons [KEY=VALUE].. [TARGET]..
83 where TARGET is config|lily|all|fonts|doc|tar|dist|release
87 config_cache = 'config.cache'
101 # Put your favourite stuff in custom.py
102 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
103 opts.Add ('prefix', 'Install prefix', '/usr/')
104 opts.Add ('out', 'Output directory', 'out-scons')
105 opts.Add ('build', 'Build directory', '.')
107 BoolOption ('warnings', 'compile with -Wall and similiar',
109 BoolOption ('debugging', 'compile with debugging symbols',
111 BoolOption ('optimising', 'compile with optimising',
113 BoolOption ('shared', 'build shared libraries',
115 BoolOption ('static', 'build static libraries',
117 BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
119 BoolOption ('verbose', 'run commands with verbose flag',
121 BoolOption ('checksums', 'use checksums instead of timestamps',
125 srcdir = Dir ('.').srcnode ().abspath
128 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
130 package = packagepython.Package (srcdir)
131 version = packagepython.version_tuple_to_str (package.version)
133 ENV = { 'PATH' : os.environ['PATH'] }
134 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
135 if os.environ.has_key(key):
136 ENV[key] = os.environ[key]
142 MAKEINFO = 'LANG= makeinfo',
143 PERL = '/usr/bin/perl',
144 PYTHON = '/usr/bin/python',
147 ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
148 LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
149 LILYPOND_BOOK_FLAGS = '',
150 LILYPOND_BOOK_FORMAT = 'texi-html',
151 LILYPOND_PY = srcdir + '/scripts/lilypond.py',
152 MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
154 PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
155 'usr/pkg/gnome/lib'),
156 os.path.join (os.environ['HOME'],
157 'usr/pkg/pango/lib')],
159 TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
160 TOPLEVEL_VERSION = version,
163 Help (usage + opts.GenerateHelpText (env))
165 map (lambda x: opts.AddOptions ((x,)), config_vars)
169 SetOption ('max_drift', 0)
171 prefix = env['prefix']
172 bindir = os.path.join (prefix, 'bin')
173 sharedir = os.path.join (prefix, 'share')
174 libdir = os.path.join (prefix, 'lib')
175 localedir = os.path.join (sharedir, 'locale')
176 sharedir_package = os.path.join (sharedir, package.name)
177 sharedir_package_version = os.path.join (sharedir_package, version)
178 lilypondprefix = sharedir_package_version
180 # post-option environment-update
186 lilypond_datadir = sharedir_package,
187 local_lilypond_datadir = sharedir_package_version,
188 lilypondprefix = lilypondprefix,
189 sharedir_package = sharedir_package,
190 sharedir_package_version = sharedir_package_version,
193 env.CacheDir (os.path.join (env['build'], '=build-cache'))
196 env.Append (CFLAGS = '-g')
197 env.Append (CXXFLAGS = '-g')
198 if env['optimising']:
199 env.Append (CFLAGS = '-O2')
200 env.Append (CXXFLAGS = '-O2')
201 env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
203 env.Append (CFLAGS = '-W ')
204 env.Append (CFLAGS = '-Wall')
205 # what about = ['-W', '-Wall', ...]?
206 env.Append (CXXFLAGS = '-W')
207 env.Append (CXXFLAGS = '-Wall')
208 env.Append (CXXFLAGS = '-Wconversion')
210 env['__verbose'] = '--verbose'
213 #env.Append (ENV = {'PKG_CONFIG_PATH' : string.join (env['PKG_CONFIG_PATH'],
215 # 'LD_LIBRARY_PATH' : string.join (env['LD_LIBRARY_PATH'],
217 # 'GUILE_LOAD_PATH' : string.join (env['GUILE_LOAD_PATH'],
220 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
222 # This is interesting, version.hh is generated automagically, just in
223 # time. Is this a .h /.hh issue? It seems to be, using config.hh (in
224 # flower/file-name.cc) works. Bug report to SCons or rename to
226 # config_h = os.path.join (outdir, 'config.hh')
227 config_h = os.path.join (outdir, 'config.h')
228 version_h = os.path.join (outdir, 'version.hh')
230 env.Alias ('config', config_cache)
233 ## Explicit dependencies
235 # Without target arguments, build lily only
236 if not COMMAND_LINE_TARGETS:
238 env.Alias ('all', '.')
241 'Documentation/user',
242 'Documentation/topdocs'])
244 env.Depends (['lily', 'flower', 'all', '.'], config_h)
245 env.Depends ('doc', ['lily', 'mf'])
246 env.Depends ('input', ['lily', 'mf'])
247 env.Depends ('doc', ['lily', 'mf'])
255 def configure (target, source, env):
256 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
257 def get_version (program):
258 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
259 pipe = os.popen (command)
260 output = pipe.read ()
263 v = re.sub (vre, '\\1', output)
264 return string.split (v, '.')
266 def test_program (lst, program, minimal, description, package):
267 sys.stdout.write ('Checking %s version... ' % program)
268 actual = get_version (program)
271 lst.append ((description, package, minimal, program,
274 sys.stdout.write (string.join (actual, '.'))
275 sys.stdout.write ('\n')
276 if actual < string.split (minimal, '.'):
277 lst.append ((description, package, minimal, program,
278 string.join (actual, '.')))
281 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
282 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
283 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
284 test_program (required, 'guile-config', '1.6', 'GUILE development',
285 'libguile-dev or guile-devel')
286 # Do not use bison 1.50 and 1.75.
287 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
289 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
293 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
294 test_program (optional, 'guile', '1.6', 'GUILE scheme',
295 'libguile-dev or guile-devel')
296 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
298 test_program (optional, 'perl', '4.0',
299 'Perl practical efficient readonly language', 'perl')
300 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
302 def CheckYYCurrentBuffer (context):
303 context.Message ('Checking for yy_current_buffer... ')
304 ret = conf.TryCompile ("""using namespace std;
305 #include <FlexLexer.h>
306 class yy_flex_lexer: public yyFlexLexer
311 yy_current_buffer = 0;
317 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
318 : CheckYYCurrentBuffer })
323 'TOPLEVEL_VERSION' : '"' + version + '"',
324 'PACKAGE': '"' + package.name + '"',
325 'DATADIR' : '"' + sharedir + '"',
326 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
327 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
328 'LOCALEDIR' : '"' + localedir + '"',
330 conf.env.Append (DEFINES = defines)
332 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
333 PYTHON_INCLUDE = os.popen (command).read ()
334 env.Append (CPPPATH = PYTHON_INCLUDE)
336 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
338 if conf.CheckCHeader (i):
339 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
340 conf.env['DEFINES'][key] = 1
342 ccheaders = ('sstream',)
344 if conf.CheckCXXHeader (i):
345 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
346 conf.env['DEFINES'][key] = 1
348 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
350 if 0 or conf.CheckFunc (i):
351 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
352 conf.env['DEFINES'][key] = 1
354 if conf.CheckYYCurrentBuffer ():
355 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
357 if conf.CheckLib ('dl'):
360 if conf.CheckLib ('kpathsea'):
361 conf.env['DEFINES']['KPATHSEA'] = 1
364 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
365 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
366 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
367 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
369 #this could happen after flower...
370 env.ParseConfig ('guile-config compile')
372 #this could happen only for compiling pango-*
374 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
375 env.ParseConfig ('pkg-config --cflags --libs pango')
376 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
377 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
379 if conf.CheckLib ('pango-1.0',
380 'pango_fc_font_map_add_decoder_find_func'):
381 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
382 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
386 print '********************************'
387 print 'Please install required packages'
389 print '%s: %s-%s or newer (found: %s %s)' % i
394 print '*************************************'
395 print 'Consider installing optional packages'
397 print '%s: %s-%s or newer (found: %s %s)' % i
399 return conf.Finish ()
401 def config_header (target, source, env):
402 config = open (str (target[0]), 'w')
403 for i in list_sort (env['DEFINES'].keys ()):
404 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
407 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
408 os.unlink (config_cache)
410 # scons: *** Calling Configure from Builders is not supported.
411 # env.Command (config_cache, None, configure)
412 if not os.path.exists (config_cache) \
413 or (os.stat ('SConstruct')[stat.ST_MTIME]
414 > os.stat (config_cache)[stat.ST_MTIME]):
415 env = configure (None, None, env)
417 # We 'should' save opts each time, but that makes config.h
418 # always out of date, and that triggers recompiles, even when
420 if FOOSUMS: #not env['checksums']:
421 ## FIXME: Is this smart, using option cache for saving
422 ## config.cache? I cannot seem to find the official method.
423 map (lambda x: opts.AddOptions ((x,)), config_vars)
424 opts.Save (config_cache, env)
425 env.Command (config_h, config_cache, config_header)
441 if not d.has_key (lst[i]):
449 for i in config_vars:
450 if env.has_key (i) and type (env[i]) == type ([]):
451 env[i] = uniquify (env[i])
453 if not FOOSUMS: #env['checksums']:
454 ## FIXME: Is this smart, using option cache for saving
455 ## config.cache? I cannot seem to find the official method.
456 map (lambda x: opts.AddOptions ((x,)), config_vars)
458 opts.Save (config_cache, env)
459 env.Command (config_h, config_cache, config_header)
461 env.Command (version_h, '#/VERSION',
462 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
464 absbuild = Dir (env['build']).abspath
465 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
467 # post-config environment update
470 run_prefix = run_prefix,
471 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
473 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
474 CPPPATH = [outdir, '#',],
475 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
477 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
478 '#/input/test', '#/input/tutorial',
479 os.path.join (absbuild, 'mf', env['out']),
480 '#/Documentation/user',
481 os.path.join (absbuild, 'Documentation',
483 os.path.join (absbuild, 'Documentation/user',
486 MAKEINFO_PATH = ['.', '#/Documentation/user',
487 os.path.join (absbuild, 'Documentation/user',
492 SConscript ('buildscripts/builder.py')
495 def symlink_tree (target, source, env):
501 if not os.path.isdir (dir):
502 if os.path.exists (dir):
506 map (mkdir, string.split (dir, os.sep))
507 def symlink (src, dst):
509 dir = os.path.dirname (dst)
512 frm = os.path.join (srcdir, src[1:])
514 depth = len (string.split (dir, '/'))
515 frm = os.path.join ('../' * depth, src, env['out'])
516 os.symlink (frm, os.path.basename (dst))
517 shutil.rmtree (run_prefix)
518 prefix = os.path.join (env['out'], 'usr')
519 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
520 (('python', 'lib/lilypond/python'),
521 # UGHR, lilypond.py uses lilypond-bin from PATH
523 ('#mf', 'share/lilypond/fonts/mf'),
524 ('mf', 'share/lilypond/fonts/afm'),
525 ('mf', 'share/lilypond/fonts/tfm'),
526 ('mf', 'share/lilypond/fonts/type1'),
527 ('#tex', 'share/lilypond/tex/source'),
528 ('mf', 'share/lilypond/tex/generate'),
529 ('#ly', 'share/lilypond/ly'),
530 ('#scm', 'share/lilypond/scm'),
531 ('#ps', 'share/lilypond/ps'),
532 ('elisp', 'share/lilypond/elisp')))
536 stamp = os.path.join (run_prefix, 'stamp')
537 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
538 env.Depends ('lily', stamp)
544 def cvs_entry_is_dir (line):
545 return line[0] == 'D' and line[-2] == '/'
547 def cvs_entry_is_file (line):
548 return line[0] == '/' and line[-2] == '/'
551 ENTRIES = os.path.join (dir, 'CVS/Entries')
552 if not os.path.exists (ENTRIES):
554 entries = open (ENTRIES).readlines ()
555 dir_entries = filter (cvs_entry_is_dir, entries)
556 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
558 return dirs + map (cvs_dirs, dirs)
561 ENTRIES = os.path.join (dir, 'CVS/Entries')
562 entries = open (ENTRIES).readlines ()
563 file_entries = filter (cvs_entry_is_file, entries)
564 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
565 return map (lambda x: os.path.join (dir, x), files)
567 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
569 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
570 foo = map (lambda x: env.TXT (x + '.txt',
571 os.path.join ('Documentation/topdocs', x)),
573 txt_files = map (lambda x: x + '.txt', readme_files)
574 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
575 tar_base = package.name + '-' + version
576 tar_name = tar_base + '.tar.gz'
577 ball_prefix = os.path.join (outdir, tar_base)
578 tar_ball = os.path.join (outdir, tar_name)
580 dist_files = src_files + txt_files
581 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
582 map (lambda x: env.Depends (tar_ball, x), ball_files)
583 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
584 'ln $SOURCE $TARGET'), dist_files)
585 tar = env.Command (tar_ball, src_files,
586 'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
587 env.Alias ('tar', tar)
589 dist_ball = os.path.join (package.release_dir, tar_name)
590 env.Command (dist_ball, tar_ball,
591 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
592 + 'ln $SOURCE $TARGET')
593 env.Depends ('dist', dist_ball)
594 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
595 patch = env.PATCH (patch_name, tar_ball)
596 env.Depends (patch_name, dist_ball)
597 env.Alias ('release', patch)
601 if os.path.exists (os.path.join (d, 'SConscript')):
602 b = os.path.join (env['build'], d, env['out'])
603 # Support clean sourcetree build (--srcdir build)
605 if os.path.abspath (b) != os.path.abspath (d):
606 env.BuildDir (b, d, duplicate = 0)
607 SConscript (os.path.join (b, 'SConscript'))