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', 'po']
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 CacheDir ("buildcache")
103 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
104 opts.Add ('prefix', 'Install prefix', '/usr/')
105 opts.Add ('out', 'Output directory', 'out-scons')
106 opts.Add ('build', 'Build directory', '.')
108 BoolOption ('warnings', 'compile with -Wall and similiar',
110 BoolOption ('debugging', 'compile with debugging symbols',
112 BoolOption ('optimising', 'compile with optimising',
114 BoolOption ('shared', 'build shared libraries',
116 BoolOption ('static', 'build static libraries',
118 BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
120 BoolOption ('verbose', 'run commands with verbose flag',
122 BoolOption ('checksums', 'use checksums instead of timestamps',
126 srcdir = Dir ('.').srcnode ().abspath
129 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
131 package = packagepython.Package (srcdir)
132 version = packagepython.version_tuple_to_str (package.version)
134 ENV = { 'PATH' : os.environ['PATH'] }
135 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
136 if os.environ.has_key(key):
137 ENV[key] = os.environ[key]
143 MAKEINFO = 'LANG= makeinfo',
144 PERL = '/usr/bin/perl',
145 PYTHON = '/usr/bin/python',
148 ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
149 LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
150 LILYPOND_BOOK_FLAGS = '',
151 LILYPOND_BOOK_FORMAT = 'texi-html',
152 LILYPOND_PY = srcdir + '/scripts/lilypond.py',
153 MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
155 PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
156 'usr/pkg/gnome/lib'),
157 os.path.join (os.environ['HOME'],
158 'usr/pkg/pango/lib')],
160 TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
161 TOPLEVEL_VERSION = version,
164 Help (usage + opts.GenerateHelpText (env))
166 map (lambda x: opts.AddOptions ((x,)), config_vars)
170 SetOption ('max_drift', 0)
172 prefix = env['prefix']
173 bindir = os.path.join (prefix, 'bin')
174 sharedir = os.path.join (prefix, 'share')
175 libdir = os.path.join (prefix, 'lib')
176 localedir = os.path.join (sharedir, 'locale')
177 sharedir_package = os.path.join (sharedir, package.name)
178 sharedir_package_version = os.path.join (sharedir_package, version)
179 lilypondprefix = sharedir_package_version
181 # post-option environment-update
187 lilypond_datadir = sharedir_package,
188 localedir = localedir,
189 local_lilypond_datadir = sharedir_package_version,
190 lilypondprefix = lilypondprefix,
191 sharedir_package = sharedir_package,
192 sharedir_package_version = sharedir_package_version,
195 env.CacheDir (os.path.join (env['build'], '=build-cache'))
198 env.Append (CFLAGS = '-g')
199 env.Append (CXXFLAGS = '-g')
200 if env['optimising']:
201 env.Append (CFLAGS = '-O2')
202 env.Append (CXXFLAGS = '-O2')
203 env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
205 env.Append (CFLAGS = '-W ')
206 env.Append (CFLAGS = '-Wall')
207 # what about = ['-W', '-Wall', ...]?
208 env.Append (CXXFLAGS = '-W')
209 env.Append (CXXFLAGS = '-Wall')
210 env.Append (CXXFLAGS = '-Wconversion')
212 env['__verbose'] = '--verbose'
215 #env.Append (ENV = {'PKG_CONFIG_PATH' : string.join (env['PKG_CONFIG_PATH'],
217 # 'LD_LIBRARY_PATH' : string.join (env['LD_LIBRARY_PATH'],
219 # 'GUILE_LOAD_PATH' : string.join (env['GUILE_LOAD_PATH'],
222 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
224 # This is interesting, version.hh is generated automagically, just in
225 # time. Is this a .h /.hh issue? It seems to be, using config.hh (in
226 # flower/file-name.cc) works. Bug report to SCons or rename to
228 # config_h = os.path.join (outdir, 'config.hh')
229 config_h = os.path.join (outdir, 'config.h')
230 version_h = os.path.join (outdir, 'version.hh')
232 env.Alias ('config', config_cache)
235 ## Explicit dependencies
237 # Without target arguments, build lily only
238 if not COMMAND_LINE_TARGETS:
240 env.Alias ('all', '.')
243 'Documentation/user',
244 'Documentation/topdocs'])
246 env.Depends (['lily', 'flower', 'all', '.'], config_h)
247 env.Depends ('doc', ['lily', 'mf'])
248 env.Depends ('input', ['lily', 'mf'])
249 env.Depends ('doc', ['lily', 'mf'])
257 def configure (target, source, env):
258 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
259 def get_version (program):
260 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
261 pipe = os.popen (command)
262 output = pipe.read ()
265 v = re.sub (vre, '\\1', output)
266 return string.split (v, '.')
268 def test_program (lst, program, minimal, description, package):
269 sys.stdout.write ('Checking %s version... ' % program)
270 actual = get_version (program)
273 lst.append ((description, package, minimal, program,
276 sys.stdout.write (string.join (actual, '.'))
277 sys.stdout.write ('\n')
278 if actual < string.split (minimal, '.'):
279 lst.append ((description, package, minimal, program,
280 string.join (actual, '.')))
283 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
284 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
285 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
286 test_program (required, 'guile-config', '1.6', 'GUILE development',
287 'libguile-dev or guile-devel')
288 # Do not use bison 1.50 and 1.75.
289 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
291 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
295 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
296 test_program (optional, 'guile', '1.6', 'GUILE scheme',
297 'libguile-dev or guile-devel')
298 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
300 test_program (optional, 'perl', '4.0',
301 'Perl practical efficient readonly language', 'perl')
302 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
304 def CheckYYCurrentBuffer (context):
305 context.Message ('Checking for yy_current_buffer... ')
306 ret = conf.TryCompile ("""using namespace std;
307 #include <FlexLexer.h>
308 class yy_flex_lexer: public yyFlexLexer
313 yy_current_buffer = 0;
319 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
320 : CheckYYCurrentBuffer })
325 'TOPLEVEL_VERSION' : '"' + version + '"',
326 'PACKAGE': '"' + package.name + '"',
327 'DATADIR' : '"' + sharedir + '"',
328 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
329 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
330 'LOCALEDIR' : '"' + localedir + '"',
332 conf.env.Append (DEFINES = defines)
334 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
335 PYTHON_INCLUDE = os.popen (command).read ()
336 env.Append (CPPPATH = PYTHON_INCLUDE)
338 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
340 if conf.CheckCHeader (i):
341 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
342 conf.env['DEFINES'][key] = 1
344 ccheaders = ('sstream',)
346 if conf.CheckCXXHeader (i):
347 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
348 conf.env['DEFINES'][key] = 1
350 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
352 if 0 or conf.CheckFunc (i):
353 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
354 conf.env['DEFINES'][key] = 1
356 if conf.CheckYYCurrentBuffer ():
357 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
359 if conf.CheckLib ('dl'):
362 if conf.CheckLib ('kpathsea'):
363 conf.env['DEFINES']['KPATHSEA'] = 1
366 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
367 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
368 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
369 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
371 #this could happen after flower...
372 env.ParseConfig ('guile-config compile')
374 #this could happen only for compiling pango-*
376 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
377 env.ParseConfig ('pkg-config --cflags --libs pango')
378 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
379 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
381 if conf.CheckLib ('pango-1.0',
382 'pango_fc_font_map_add_decoder_find_func'):
383 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
384 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
388 print '********************************'
389 print 'Please install required packages'
391 print '%s: %s-%s or newer (found: %s %s)' % i
396 print '*************************************'
397 print 'Consider installing optional packages'
399 print '%s: %s-%s or newer (found: %s %s)' % i
401 return conf.Finish ()
403 def config_header (target, source, env):
404 config = open (str (target[0]), 'w')
405 for i in list_sort (env['DEFINES'].keys ()):
406 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
409 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
410 os.unlink (config_cache)
412 # scons: *** Calling Configure from Builders is not supported.
413 # env.Command (config_cache, None, configure)
414 if not os.path.exists (config_cache) \
415 or (os.stat ('SConstruct')[stat.ST_MTIME]
416 > os.stat (config_cache)[stat.ST_MTIME]):
417 env = configure (None, None, env)
419 # We 'should' save opts each time, but that makes config.h
420 # always out of date, and that triggers recompiles, even when
422 if FOOSUMS: #not env['checksums']:
423 ## FIXME: Is this smart, using option cache for saving
424 ## config.cache? I cannot seem to find the official method.
425 map (lambda x: opts.AddOptions ((x,)), config_vars)
426 opts.Save (config_cache, env)
427 env.Command (config_h, config_cache, config_header)
443 if not d.has_key (lst[i]):
451 for i in config_vars:
452 if env.has_key (i) and type (env[i]) == type ([]):
453 env[i] = uniquify (env[i])
455 if not FOOSUMS: #env['checksums']:
456 ## FIXME: Is this smart, using option cache for saving
457 ## config.cache? I cannot seem to find the official method.
458 map (lambda x: opts.AddOptions ((x,)), config_vars)
460 opts.Save (config_cache, env)
461 env.Command (config_h, config_cache, config_header)
463 env.Command (version_h, '#/VERSION',
464 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
466 absbuild = Dir (env['build']).abspath
467 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
469 # post-config environment update
472 run_prefix = run_prefix,
473 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
475 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
476 CPPPATH = [outdir, '#',],
477 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
479 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
480 '#/input/test', '#/input/tutorial',
481 os.path.join (absbuild, 'mf', env['out']),
482 '#/Documentation/user',
483 os.path.join (absbuild, 'Documentation',
485 os.path.join (absbuild, 'Documentation/user',
488 MAKEINFO_PATH = ['.', '#/Documentation/user',
489 os.path.join (absbuild, 'Documentation/user',
494 SConscript ('buildscripts/builder.py')
497 def symlink_tree (target, source, env):
503 if not os.path.isdir (dir):
504 if os.path.exists (dir):
508 map (mkdir, string.split (dir, os.sep))
509 def symlink (src, dst):
511 dir = os.path.dirname (dst)
514 frm = os.path.join (srcdir, src[1:])
516 depth = len (string.split (dir, '/'))
517 frm = os.path.join ('../' * depth, src, env['out'])
518 os.symlink (frm, os.path.basename (dst))
519 shutil.rmtree (run_prefix)
520 prefix = os.path.join (env['out'], 'usr')
521 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
522 (('python', 'lib/lilypond/python'),
523 # UGHR, lilypond.py uses lilypond-bin from PATH
525 ('#mf', 'share/lilypond/fonts/mf'),
526 ('mf', 'share/lilypond/fonts/afm'),
527 ('mf', 'share/lilypond/fonts/tfm'),
528 ('mf', 'share/lilypond/fonts/type1'),
529 ('#tex', 'share/lilypond/tex/source'),
530 ('mf', 'share/lilypond/tex/generate'),
531 ('#ly', 'share/lilypond/ly'),
532 ('#scm', 'share/lilypond/scm'),
533 ('#ps', 'share/lilypond/ps'),
534 ('elisp', 'share/lilypond/elisp')))
538 stamp = os.path.join (run_prefix, 'stamp')
539 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
540 env.Depends ('lily', stamp)
546 def cvs_entry_is_dir (line):
547 return line[0] == 'D' and line[-2] == '/'
549 def cvs_entry_is_file (line):
550 return line[0] == '/' and line[-2] == '/'
553 ENTRIES = os.path.join (dir, 'CVS/Entries')
554 if not os.path.exists (ENTRIES):
556 entries = open (ENTRIES).readlines ()
557 dir_entries = filter (cvs_entry_is_dir, entries)
558 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
560 return dirs + map (cvs_dirs, dirs)
563 ENTRIES = os.path.join (dir, 'CVS/Entries')
564 entries = open (ENTRIES).readlines ()
565 file_entries = filter (cvs_entry_is_file, entries)
566 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
567 return map (lambda x: os.path.join (dir, x), files)
569 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
571 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
572 foo = map (lambda x: env.TXT (x + '.txt',
573 os.path.join ('Documentation/topdocs', x)),
575 txt_files = map (lambda x: x + '.txt', readme_files)
576 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
577 tar_base = package.name + '-' + version
578 tar_name = tar_base + '.tar.gz'
579 ball_prefix = os.path.join (outdir, tar_base)
580 tar_ball = os.path.join (outdir, tar_name)
582 dist_files = src_files + txt_files
583 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
584 map (lambda x: env.Depends (tar_ball, x), ball_files)
585 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
586 'ln $SOURCE $TARGET'), dist_files)
587 tar = env.Command (tar_ball, src_files,
588 'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
589 env.Alias ('tar', tar)
591 dist_ball = os.path.join (package.release_dir, tar_name)
592 env.Command (dist_ball, tar_ball,
593 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
594 + 'ln $SOURCE $TARGET')
595 env.Depends ('dist', dist_ball)
596 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
597 patch = env.PATCH (patch_name, tar_ball)
598 env.Depends (patch_name, dist_ball)
599 env.Alias ('release', patch)
602 if os.path.exists (os.path.join (d, 'SConscript')):
603 b = os.path.join (env['build'], d, env['out'])
604 # Support clean sourcetree build (--srcdir build)
606 if os.path.abspath (b) != os.path.abspath (d):
607 env.BuildDir (b, d, duplicate = 0)
608 SConscript (os.path.join (b, 'SConscript'))