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', 'input', 'scripts', 'elisp',
77 'buildscripts', 'cygwin', 'debian']
80 scons [KEY=VALUE].. [TARGET]..
82 where TARGET is config|lily|all|fonts|doc|tar|dist|release
86 config_cache = 'config.cache'
100 # Put your favourite stuff in custom.py
101 opts = Options ([config_cache, 'custom.py'], ARGUMENTS)
102 opts.Add ('prefix', 'Install prefix', '/usr/')
103 opts.Add ('out', 'Output directory', 'out-scons')
104 opts.Add ('build', 'Build directory', '.')
106 BoolOption ('warnings', 'compile with -Wall and similiar',
108 BoolOption ('debugging', 'compile with debugging symbols',
110 BoolOption ('optimising', 'compile with optimising',
112 BoolOption ('shared', 'build shared libraries',
114 BoolOption ('static', 'build static libraries',
116 BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)',
118 BoolOption ('verbose', 'run commands with verbose flag',
120 BoolOption ('checksums', 'use checksums instead of timestamps',
124 srcdir = Dir ('.').srcnode ().abspath
127 sys.path.append (os.path.join (srcdir, 'stepmake', 'bin'))
129 package = packagepython.Package (srcdir)
130 version = packagepython.version_tuple_to_str (package.version)
132 ENV = { 'PATH' : os.environ['PATH'] }
133 for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']:
134 if os.environ.has_key(key):
135 ENV[key] = os.environ[key]
141 MAKEINFO = 'LANG= makeinfo',
142 PERL = '/usr/bin/perl',
143 PYTHON = '/usr/bin/python',
146 ABC2LY_PY = srcdir + '/scripts/abc2ly.py',
147 LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py',
148 LILYPOND_BOOK_FLAGS = '',
149 LILYPOND_BOOK_FORMAT = 'texi-html',
150 LILYPOND_PY = srcdir + '/scripts/lilypond.py',
151 MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py',
153 PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'],
154 'usr/pkg/gnome/lib'),
155 os.path.join (os.environ['HOME'],
156 'usr/pkg/pango/lib')],
158 TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper',
159 TOPLEVEL_VERSION = version,
162 Help (usage + opts.GenerateHelpText (env))
164 map (lambda x: opts.AddOptions ((x,)), config_vars)
168 SetOption ('max_drift', 0)
170 prefix = env['prefix']
171 bindir = os.path.join (prefix, 'bin')
172 sharedir = os.path.join (prefix, 'share')
173 libdir = os.path.join (prefix, 'lib')
174 localedir = os.path.join (sharedir, 'locale')
175 sharedir_package = os.path.join (sharedir, package.name)
176 sharedir_package_version = os.path.join (sharedir_package, version)
177 lilypondprefix = sharedir_package_version
179 # post-option environment-update
185 lilypond_datadir = sharedir_package,
186 local_lilypond_datadir = sharedir_package_version,
187 lilypondprefix = lilypondprefix,
188 sharedir_package = sharedir_package,
189 sharedir_package_version = sharedir_package_version,
192 env.CacheDir (os.path.join (env['build'], '=build-cache'))
195 env.Append (CFLAGS = '-g')
196 env.Append (CXXFLAGS = '-g')
197 if env['optimising']:
198 env.Append (CFLAGS = '-O2')
199 env.Append (CXXFLAGS = '-O2')
200 env.Append (CXXFLAGS = '-DSTRING_UTILS_INLINED')
202 env.Append (CFLAGS = '-W ')
203 env.Append (CFLAGS = '-Wall')
204 # what about = ['-W', '-Wall', ...]?
205 env.Append (CXXFLAGS = '-W')
206 env.Append (CXXFLAGS = '-Wall')
207 env.Append (CXXFLAGS = '-Wconversion')
209 env['__verbose'] = '--verbose'
212 #env.Append (ENV = {'PKG_CONFIG_PATH' : string.join (env['PKG_CONFIG_PATH'],
214 # 'LD_LIBRARY_PATH' : string.join (env['LD_LIBRARY_PATH'],
216 # 'GUILE_LOAD_PATH' : string.join (env['GUILE_LOAD_PATH'],
219 outdir = os.path.join (Dir (env['build']).abspath, env['out'])
220 config_h = os.path.join (outdir, 'config.h')
221 version_h = os.path.join (outdir, 'version.hh')
222 config_h = os.path.join (outdir, 'config.h')
223 env.Alias ('config', config_h)
226 ## Explicit dependencies
228 # Without target arguments, build lily only
229 if not COMMAND_LINE_TARGETS:
231 env.Alias ('all', '.')
234 'Documentation/user',
235 'Documentation/topdocs'])
237 env.Depends ('doc', ['lily', 'mf'])
238 env.Depends ('input', ['lily', 'mf'])
239 env.Depends ('doc', ['lily', 'mf'])
247 def configure (target, source, env):
248 vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL)
249 def get_version (program):
250 command = '(%(program)s --version || %(program)s -V) 2>&1' % vars ()
251 pipe = os.popen (command)
252 output = pipe.read ()
255 v = re.sub (vre, '\\1', output)
256 return string.split (v, '.')
258 def test_program (lst, program, minimal, description, package):
259 sys.stdout.write ('Checking %s version... ' % program)
260 actual = get_version (program)
263 lst.append ((description, package, minimal, program,
266 sys.stdout.write (string.join (actual, '.'))
267 sys.stdout.write ('\n')
268 if actual < string.split (minimal, '.'):
269 lst.append ((description, package, minimal, program,
270 string.join (actual, '.')))
273 test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc')
274 test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++')
275 test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python')
276 test_program (required, 'guile-config', '1.6', 'GUILE development',
277 'libguile-dev or guile-devel')
278 # Do not use bison 1.50 and 1.75.
279 test_program (required, 'bison', '1.25', 'Bison -- parser generator',
281 test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex')
285 test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo')
286 test_program (optional, 'guile', '1.6', 'GUILE scheme',
287 'libguile-dev or guile-devel')
288 test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1',
290 test_program (optional, 'perl', '4.0',
291 'Perl practical efficient readonly language', 'perl')
292 #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar')
294 def CheckYYCurrentBuffer (context):
295 context.Message ('Checking for yy_current_buffer... ')
296 ret = conf.TryCompile ("""using namespace std;
297 #include <FlexLexer.h>
298 class yy_flex_lexer: public yyFlexLexer
303 yy_current_buffer = 0;
308 conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer'
309 : CheckYYCurrentBuffer })
314 'TOPLEVEL_VERSION' : '"' + version + '"',
315 'PACKAGE': '"' + package.name + '"',
316 'DATADIR' : '"' + sharedir + '"',
317 'LILYPOND_DATADIR' : '"' + sharedir_package + '"',
318 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"',
319 'LOCALEDIR' : '"' + localedir + '"',
321 conf.env.Append (DEFINES = defines)
323 command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #"
324 PYTHON_INCLUDE = os.popen (command).read ()
325 env.Append (CPPPATH = PYTHON_INCLUDE)
327 headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h')
329 if conf.CheckCHeader (i):
330 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
331 conf.env['DEFINES'][key] = 1
333 ccheaders = ('sstream',)
335 if conf.CheckCXXHeader (i):
336 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
337 conf.env['DEFINES'][key] = 1
339 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf')
341 if 0 or conf.CheckFunc (i):
342 key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i))
343 conf.env['DEFINES'][key] = 1
345 if conf.CheckYYCurrentBuffer ():
346 conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1
348 if conf.CheckLib ('dl'):
351 if conf.CheckLib ('kpathsea'):
352 conf.env['DEFINES']['KPATHSEA'] = 1
355 if conf.CheckLib ('kpathsea', 'kpse_find_file'):
356 conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1'
357 if conf.CheckLib ('kpathsea', 'kpse_find_tfm'):
358 conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1'
360 #this could happen after flower...
361 env.ParseConfig ('guile-config compile')
363 #this could happen only for compiling pango-*
365 env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0')
366 env.ParseConfig ('pkg-config --cflags --libs pango')
367 if conf.CheckCHeader ('pango/pangofc-fontmap.h'):
368 conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1'
370 if conf.CheckLib ('pango-1.0',
371 'pango_fc_font_map_add_decoder_find_func'):
372 conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1'
373 conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1'
377 print '********************************'
378 print 'Please install required packages'
380 print '%s: %s-%s or newer (found: %s %s)' % i
385 print '*************************************'
386 print 'Consider installing optional packages'
388 print '%s: %s-%s or newer (found: %s %s)' % i
390 return conf.Finish ()
392 def config_header (target, source, env):
393 config = open (str (target[0]), 'w')
394 for i in list_sort (env['DEFINES'].keys ()):
395 config.write ('#define %s %s\n' % (i, env['DEFINES'][i]))
398 if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS:
399 os.unlink (config_cache)
401 # scons: *** Calling Configure from Builders is not supported.
402 # env.Command (config_cache, None, configure)
403 if not os.path.exists (config_cache) \
404 or (os.stat ('SConstruct')[stat.ST_MTIME]
405 > os.stat (config_cache)[stat.ST_MTIME]):
406 env = configure (None, None, env)
408 # We 'should' save opts each time, but that makes config.h
409 # always out of date, and that triggers recompiles, even when
411 if FOOSUMS: #not env['checksums']:
413 ## FIXME: Is this smart, using option cache for saving
414 ## config.cache? I cannot seem to find the official method.
415 map (lambda x: opts.AddOptions ((x,)), config_vars)
416 opts.Save (config_cache, env)
418 env.Command (config_h, config_cache, config_header)
434 if not d.has_key (lst[i]):
442 for i in config_vars:
443 if env.has_key (i) and type (env[i]) == type ([]):
444 env[i] = uniquify (env[i])
446 if not FOOSUMS: #env['checksums']:
447 ## FIXME: Is this smart, using option cache for saving
448 ## config.cache? I cannot seem to find the official method.
449 map (lambda x: opts.AddOptions ((x,)), config_vars)
451 opts.Save (config_cache, env)
452 env.Command (config_h, config_cache, config_header)
454 env.Command (version_h, '#/VERSION',
455 '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET')
457 absbuild = Dir (env['build']).abspath
458 run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr'))
460 # post-config environment update
463 run_prefix = run_prefix,
464 LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'),
466 LIBPATH = [os.path.join (absbuild, 'flower', env['out']),],
467 CPPPATH = [outdir, '#',],
468 LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'],
470 LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression',
471 '#/input/test', '#/input/tutorial',
472 os.path.join (absbuild, 'mf', env['out']),
473 '#/Documentation/user',
474 os.path.join (absbuild, 'Documentation',
476 os.path.join (absbuild, 'Documentation/user',
479 MAKEINFO_PATH = ['.', '#/Documentation/user',
480 os.path.join (absbuild, 'Documentation/user',
485 SConscript ('buildscripts/builder.py')
488 def symlink_tree (target, source, env):
494 if not os.path.isdir (dir):
495 if os.path.exists (dir):
499 map (mkdir, string.split (dir, os.sep))
500 def symlink (src, dst):
502 dir = os.path.dirname (dst)
505 frm = os.path.join (srcdir, src[1:])
507 depth = len (string.split (dir, '/'))
508 frm = os.path.join ('../' * depth, src, env['out'])
509 os.symlink (frm, os.path.basename (dst))
510 shutil.rmtree (run_prefix)
511 prefix = os.path.join (env['out'], 'usr')
512 map (lambda x: symlink (x[0], os.path.join (prefix, x[1])),
513 (('python', 'lib/lilypond/python'),
514 # UGHR, lilypond.py uses lilypond-bin from PATH
516 ('#mf', 'share/lilypond/fonts/mf'),
517 ('mf', 'share/lilypond/fonts/afm'),
518 ('mf', 'share/lilypond/fonts/tfm'),
519 ('mf', 'share/lilypond/fonts/type1'),
520 ('#tex', 'share/lilypond/tex/source'),
521 ('mf', 'share/lilypond/tex/generate'),
522 ('#ly', 'share/lilypond/ly'),
523 ('#scm', 'share/lilypond/scm'),
524 ('#ps', 'share/lilypond/ps'),
525 ('elisp', 'share/lilypond/elisp')))
529 stamp = os.path.join (run_prefix, 'stamp')
530 env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET'])
531 env.Depends ('lily', stamp)
537 def cvs_entry_is_dir (line):
538 return line[0] == 'D' and line[-2] == '/'
540 def cvs_entry_is_file (line):
541 return line[0] == '/' and line[-2] == '/'
544 ENTRIES = os.path.join (dir, 'CVS/Entries')
545 if not os.path.exists (ENTRIES):
547 entries = open (ENTRIES).readlines ()
548 dir_entries = filter (cvs_entry_is_dir, entries)
549 dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]),
551 return dirs + map (cvs_dirs, dirs)
554 ENTRIES = os.path.join (dir, 'CVS/Entries')
555 entries = open (ENTRIES).readlines ()
556 file_entries = filter (cvs_entry_is_file, entries)
557 files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries)
558 return map (lambda x: os.path.join (dir, x), files)
560 #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.'))
562 readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS']
563 foo = map (lambda x: env.TXT (x + '.txt',
564 os.path.join ('Documentation/topdocs', x)),
566 txt_files = map (lambda x: x + '.txt', readme_files)
567 src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs))
568 tar_base = package.name + '-' + version
569 tar_name = tar_base + '.tar.gz'
570 ball_prefix = os.path.join (outdir, tar_base)
571 tar_ball = os.path.join (outdir, tar_name)
573 dist_files = src_files + txt_files
574 ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files)
575 map (lambda x: env.Depends (tar_ball, x), ball_files)
576 map (lambda x: env.Command (os.path.join (ball_prefix, x), x,
577 'ln $SOURCE $TARGET'), dist_files)
578 tar = env.Command (tar_ball, src_files,
579 'tar czf $TARGET -C $TARGET.dir %s' % tar_base)
580 env.Alias ('tar', tar)
582 dist_ball = os.path.join (package.release_dir, tar_name)
583 env.Command (dist_ball, tar_ball,
584 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \
585 + 'ln $SOURCE $TARGET')
586 env.Depends ('dist', dist_ball)
587 patch_name = os.path.join (outdir, tar_base + '.diff.gz')
588 patch = env.PATCH (patch_name, tar_ball)
589 env.Depends (patch_name, dist_ball)
590 env.Alias ('release', patch)
594 if os.path.exists (os.path.join (d, 'SConscript')):
595 b = os.path.join (env['build'], d, env['out'])
596 # Support clean sourcetree build (--srcdir build)
598 if os.path.abspath (b) != os.path.abspath (d):
599 env.BuildDir (b, d, duplicate = 0)
600 SConscript (os.path.join (b, 'SConscript'))