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 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 localedir = localedir,
188 local_lilypond_datadir = sharedir_package_version,
189 lilypondprefix = lilypondprefix,
190 sharedir_package = sharedir_package,
191 sharedir_package_version = sharedir_package_version,
194 env.CacheDir (os.path.join (env['build'], '=build-cache'))
197 env.Append (CFLAGS = '-g')
198 env.Append (CXXFLAGS = '-g')
199 if env['optimising']:
200 env.Append (CFLAGS = '-O2')
201 env.Append (CXXFLAGS = '-O2')
202 env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
204 env.Append (CFLAGS = '-W ')
205 env.Append (CFLAGS = '-Wall')
206 # what about = ['-W', '-Wall', ...]?
207 env.Append (CXXFLAGS = '-W')
208 env.Append (CXXFLAGS = '-Wall')
209 env.Append (CXXFLAGS = '-Wconversion')
211 env['__verbose'] = '--verbose'
214 #env.Append (ENV = {'PKG_CONFIG_PATH' : string.join (env['PKG_CONFIG_PATH'],
216 # 'LD_LIBRARY_PATH' : string.join (env['LD_LIBRARY_PATH'],
218 # 'GUILE_LOAD_PATH' : string.join (env['GUILE_LOAD_PATH'],
221 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
223 # This is interesting, version.hh is generated automagically, just in
224 # time. Is this a .h /.hh issue? It seems to be, using config.hh (in
225 # flower/file-name.cc) works. Bug report to SCons or rename to
227 # config_h = os.path.join (outdir, 'config.hh')
228 config_h = os.path.join (outdir, 'config.h')
229 version_h = os.path.join (outdir, 'version.hh')
231 env.Alias ('config', config_cache)
234 ## Explicit dependencies
236 # Without target arguments, build lily only
237 if not COMMAND_LINE_TARGETS:
239 env.Alias ('all', '.')
242 'Documentation/user',
243 'Documentation/topdocs'])
245 env.Depends (['lily', 'flower', 'all', '.'], config_h)
246 env.Depends ('doc', ['lily', 'mf'])
247 env.Depends ('input', ['lily', 'mf'])
248 env.Depends ('doc', ['lily', 'mf'])
256 def configure (target, source, env):
257 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
258 def get_version (program):
259 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
260 pipe = os.popen (command)
261 output = pipe.read ()
264 v = re.sub (vre, '\\1', output)
265 return string.split (v, '.')
267 def test_program (lst, program, minimal, description, package):
268 sys.stdout.write ('Checking %s version... ' % program)
269 actual = get_version (program)
272 lst.append ((description, package, minimal, program,
275 sys.stdout.write (string.join (actual, '.'))
276 sys.stdout.write ('\n')
277 if actual < string.split (minimal, '.'):
278 lst.append ((description, package, minimal, program,
279 string.join (actual, '.')))
282 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
283 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
284 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
285 test_program (required, 'guile-config', '1.6', 'GUILE development',
286 'libguile-dev or guile-devel')
287 # Do not use bison 1.50 and 1.75.
288 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
290 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
294 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
295 test_program (optional, 'guile', '1.6', 'GUILE scheme',
296 'libguile-dev or guile-devel')
297 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
299 test_program (optional, 'perl', '4.0',
300 'Perl practical efficient readonly language', 'perl')
301 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
303 def CheckYYCurrentBuffer (context):
304 context.Message ('Checking for yy_current_buffer... ')
305 ret = conf.TryCompile ("""using namespace std;
306 #include <FlexLexer.h>
307 class yy_flex_lexer: public yyFlexLexer
312 yy_current_buffer = 0;
318 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
319 : CheckYYCurrentBuffer })
324 'TOPLEVEL_VERSION' : '"' + version + '"',
325 'PACKAGE': '"' + package.name + '"',
326 'DATADIR' : '"' + sharedir + '"',
327 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
328 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
329 'LOCALEDIR' : '"' + localedir + '"',
331 conf.env.Append (DEFINES = defines)
333 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
334 PYTHON_INCLUDE = os.popen (command).read ()
335 env.Append (CPPPATH = PYTHON_INCLUDE)
337 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
339 if conf.CheckCHeader (i):
340 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
341 conf.env['DEFINES'][key] = 1
343 ccheaders = ('sstream',)
345 if conf.CheckCXXHeader (i):
346 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
347 conf.env['DEFINES'][key] = 1
349 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
351 if 0 or conf.CheckFunc (i):
352 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
353 conf.env['DEFINES'][key] = 1
355 if conf.CheckYYCurrentBuffer ():
356 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
358 if conf.CheckLib ('dl'):
361 if conf.CheckLib ('kpathsea'):
362 conf.env['DEFINES']['KPATHSEA'] = 1
365 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
366 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
367 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
368 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
370 #this could happen after flower...
371 env.ParseConfig ('guile-config compile')
373 #this could happen only for compiling pango-*
375 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
376 env.ParseConfig ('pkg-config --cflags --libs pango')
377 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
378 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
380 if conf.CheckLib ('pango-1.0',
381 'pango_fc_font_map_add_decoder_find_func'):
382 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
383 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
387 print '********************************'
388 print 'Please install required packages'
390 print '%s: %s-%s or newer (found: %s %s)' % i
395 print '*************************************'
396 print 'Consider installing optional packages'
398 print '%s: %s-%s or newer (found: %s %s)' % i
400 return conf.Finish ()
402 def config_header (target, source, env):
403 config = open (str (target[0]), 'w')
404 for i in list_sort (env['DEFINES'].keys ()):
405 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
408 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
409 os.unlink (config_cache)
411 # scons: *** Calling Configure from Builders is not supported.
412 # env.Command (config_cache, None, configure)
413 if not os.path.exists (config_cache) \
414 or (os.stat ('SConstruct')[stat.ST_MTIME]
415 > os.stat (config_cache)[stat.ST_MTIME]):
416 env = configure (None, None, env)
418 # We 'should' save opts each time, but that makes config.h
419 # always out of date, and that triggers recompiles, even when
421 if FOOSUMS: #not env['checksums']:
422 ## FIXME: Is this smart, using option cache for saving
423 ## config.cache? I cannot seem to find the official method.
424 map (lambda x: opts.AddOptions ((x,)), config_vars)
425 opts.Save (config_cache, env)
426 env.Command (config_h, config_cache, config_header)
442 if not d.has_key (lst[i]):
450 for i in config_vars:
451 if env.has_key (i) and type (env[i]) == type ([]):
452 env[i] = uniquify (env[i])
454 if not FOOSUMS: #env['checksums']:
455 ## FIXME: Is this smart, using option cache for saving
456 ## config.cache? I cannot seem to find the official method.
457 map (lambda x: opts.AddOptions ((x,)), config_vars)
459 opts.Save (config_cache, env)
460 env.Command (config_h, config_cache, config_header)
462 env.Command (version_h, '#/VERSION',
463 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
465 absbuild = Dir (env['build']).abspath
466 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
468 # post-config environment update
471 run_prefix = run_prefix,
472 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
474 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
475 CPPPATH = [outdir, '#',],
476 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
478 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
479 '#/input/test', '#/input/tutorial',
480 os.path.join (absbuild, 'mf', env['out']),
481 '#/Documentation/user',
482 os.path.join (absbuild, 'Documentation',
484 os.path.join (absbuild, 'Documentation/user',
487 MAKEINFO_PATH = ['.', '#/Documentation/user',
488 os.path.join (absbuild, 'Documentation/user',
493 SConscript ('buildscripts/builder.py')
496 def symlink_tree (target, source, env):
502 if not os.path.isdir (dir):
503 if os.path.exists (dir):
507 map (mkdir, string.split (dir, os.sep))
508 def symlink (src, dst):
510 dir = os.path.dirname (dst)
513 frm = os.path.join (srcdir, src[1:])
515 depth = len (string.split (dir, '/'))
516 frm = os.path.join ('../' * depth, src, env['out'])
517 os.symlink (frm, os.path.basename (dst))
518 shutil.rmtree (run_prefix)
519 prefix = os.path.join (env['out'], 'usr')
520 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
521 (('python', 'lib/lilypond/python'),
522 # UGHR, lilypond.py uses lilypond-bin from PATH
524 ('#mf', 'share/lilypond/fonts/mf'),
525 ('mf', 'share/lilypond/fonts/afm'),
526 ('mf', 'share/lilypond/fonts/tfm'),
527 ('mf', 'share/lilypond/fonts/type1'),
528 ('#tex', 'share/lilypond/tex/source'),
529 ('mf', 'share/lilypond/tex/generate'),
530 ('#ly', 'share/lilypond/ly'),
531 ('#scm', 'share/lilypond/scm'),
532 ('#ps', 'share/lilypond/ps'),
533 ('elisp', 'share/lilypond/elisp')))
537 stamp = os.path.join (run_prefix, 'stamp')
538 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
539 env.Depends ('lily', stamp)
545 def cvs_entry_is_dir (line):
546 return line[0] == 'D' and line[-2] == '/'
548 def cvs_entry_is_file (line):
549 return line[0] == '/' and line[-2] == '/'
552 ENTRIES = os.path.join (dir, 'CVS/Entries')
553 if not os.path.exists (ENTRIES):
555 entries = open (ENTRIES).readlines ()
556 dir_entries = filter (cvs_entry_is_dir, entries)
557 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
559 return dirs + map (cvs_dirs, dirs)
562 ENTRIES = os.path.join (dir, 'CVS/Entries')
563 entries = open (ENTRIES).readlines ()
564 file_entries = filter (cvs_entry_is_file, entries)
565 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
566 return map (lambda x: os.path.join (dir, x), files)
568 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
570 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
571 foo = map (lambda x: env.TXT (x + '.txt',
572 os.path.join ('Documentation/topdocs', x)),
574 txt_files = map (lambda x: x + '.txt', readme_files)
575 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
576 tar_base = package.name + '-' + version
577 tar_name = tar_base + '.tar.gz'
578 ball_prefix = os.path.join (outdir, tar_base)
579 tar_ball = os.path.join (outdir, tar_name)
581 dist_files = src_files + txt_files
582 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
583 map (lambda x: env.Depends (tar_ball, x), ball_files)
584 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
585 'ln $SOURCE $TARGET'), dist_files)
586 tar = env.Command (tar_ball, src_files,
587 'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
588 env.Alias ('tar', tar)
590 dist_ball = os.path.join (package.release_dir, tar_name)
591 env.Command (dist_ball, tar_ball,
592 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
593 + 'ln $SOURCE $TARGET')
594 env.Depends ('dist', dist_ball)
595 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
596 patch = env.PATCH (patch_name, tar_ball)
597 env.Depends (patch_name, dist_ball)
598 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'))