# -*-python-*- ''' Experimental scons (www.scons.org) building. Usage scons TARGET build from source directory ./TARGET (not recursive) Configure, build scons [config] # configure scons # build all Run from build tree run=$(pwd)/out-scons/usr export LOCALE=$run/share/locale export TEXMF='{'$run/share/lilypond,$(kpsexpand '$TEXMF')'}' PATH=$run/bin:$PATH #optionally, if you do not use custom.py below #export LILYPONDPREFIX=$run/share/lilypond lilypond-bin input/simple Other targets scons mf-essential # build minimal mf stuff scons doc # build web doc scons config # reconfigure scons install # install scons -c # clean scons -h # help scons / # build *everything* (including installation) Options (see scons -h) scons build=DIR # clean scrdir build, output below DIR scons out=DIR # write output for alterative config to DIR Optional custom.py import os out='out-scons' optimising=0 debugging=1 gui=1 os.path.join (os.getcwd (), '=install') prefix=os.path.join (os.environ['HOME'], 'usr', 'pkg', 'lilypond') ''' # TODO: # * add missing dirs: # - input/mutopia # - vim # * more program configure tests (mfont, ...?) # * more fine-grained config.hh -- move lilypondprefix to version.hh? # - config.hh: changes after system upgrades, affects all files # - version.hh: prefix, version etc? affects few # * grep FIXME $(find . -name 'S*t') import re import glob import os import string import sys import stat import shutil # duh, we need 0.95.1 EnsureSConsVersion (0, 95) # SConscripts are only needed in directories where something needs # to be done, building or installing subdirs = ['flower', 'lily', 'mf', 'scm', 'ly', 'scripts', 'elisp', 'buildscripts', 'po', 'Documentation', 'Documentation/user', 'Documentation/topdocs', 'Documentation/bibliography', 'input', 'input/regression', 'input/test', 'input/template', 'cygwin', 'debian', ] usage = r'''Usage: scons [KEY=VALUE].. [TARGET|DIR].. TARGETS: clean, config, doc, dist, install, mf-essential, po-update, realclean, release, tar, TAGS ''' config_cache = 'config.cache' config_vars = ( 'BASH', 'CFLAGS', 'CPPPATH', 'CXXFLAGS', 'DEFINES', 'LIBS', 'LINKFLAGS', 'METAFONT', 'PERL', 'PYTHON', ) # Put your favourite stuff in custom.py opts = Options ([config_cache, 'custom.py'], ARGUMENTS) opts.Add ('prefix', 'Install prefix', '/usr/') opts.Add ('out', 'Output directory', 'out-scons') opts.Add ('build', 'Build directory', '.') opts.AddOptions ( BoolOption ('warnings', 'compile with -Wall and similiar', 1), BoolOption ('debugging', 'compile with debugging symbols', 0), BoolOption ('optimising', 'compile with optimising', 1), BoolOption ('shared', 'build shared libraries', 0), BoolOption ('static', 'build static libraries', 1), BoolOption ('gui', 'build with GNOME backend (EXPERIMENTAL)', 1), BoolOption ('verbose', 'run commands with verbose flag', 0), BoolOption ('checksums', 'use checksums instead of timestamps', 1), ) srcdir = Dir ('.').srcnode ().abspath #ugh sys.path.append (os.path.join (srcdir, 'stepmake', 'bin')) import packagepython package = packagepython.Package (srcdir) version = packagepython.version_tuple_to_str (package.version) ENV = { 'PATH' : os.environ['PATH'] } for key in ['LD_LIBRARY_PATH', 'GUILE_LOAD_PATH', 'PKG_CONFIG_PATH']: if os.environ.has_key (key): ENV[key] = os.environ[key] env = Environment ( ENV = ENV, BASH = '/bin/bash', MAKEINFO = 'LANG= makeinfo', PERL = '/usr/bin/perl', PYTHON = '/usr/bin/python', SH = '/bin/sh', ABC2LY_PY = srcdir + '/scripts/abc2ly.py', LILYPOND_BOOK = srcdir + '/scripts/lilypond-book.py', LILYPOND_BOOK_FLAGS = '', LILYPOND_BOOK_FORMAT = 'texi-html', LILYPOND_PY = srcdir + '/scripts/lilypond.py', MF_TO_TABLE_PY = srcdir + '/buildscripts/mf-to-table.py', PKG_CONFIG_PATH = [os.path.join (os.environ['HOME'], 'usr/pkg/gnome/lib'), os.path.join (os.environ['HOME'], 'usr/pkg/pango/lib')], MFMODE = 'ljfour', TEXINFO_PAPERSIZE_OPTION = '-t @afourpaper', TOPLEVEL_VERSION = version, ) Help (usage + opts.GenerateHelpText (env)) # Add all config_vars to opts, so that they will be read and saved # together with the other configure options. map (lambda x: opts.AddOptions ((x,)), config_vars) opts.Update (env) env['checksums'] = 1 if env['checksums']: SetOption ('max_drift', 0) absbuild = Dir (env['build']).abspath outdir = os.path.join (Dir (env['build']).abspath, env['out']) run_prefix = os.path.join (absbuild, os.path.join (env['out'], 'usr')) CacheDir (os.path.join (outdir, 'build-cache')) if env['debugging']: # No need to set $LILYPONDPREFIX to run lily, but cannot install... env['prefix'] = run_prefix prefix = env['prefix'] bindir = os.path.join (prefix, 'bin') sharedir = os.path.join (prefix, 'share') libdir = os.path.join (prefix, 'lib') localedir = os.path.join (sharedir, 'locale') sharedir_package = os.path.join (sharedir, package.name) sharedir_package_version = os.path.join (sharedir_package, version) lilypondprefix = sharedir_package_version # post-option environment-update env.Append ( srcdir = srcdir, bindir = bindir, sharedir = sharedir, lilypond_datadir = sharedir_package, localedir = localedir, local_lilypond_datadir = sharedir_package_version, lilypondprefix = lilypondprefix, sharedir_package = sharedir_package, sharedir_package_version = sharedir_package_version, ) if env['debugging']: env.Append (CFLAGS = '-g') env.Append (CXXFLAGS = '-g') if env['optimising']: env.Append (CFLAGS = '-O2') env.Append (CXXFLAGS = ['-O2', '-DSTRING_UTILS_INLINED']) if env['warnings']: env.Append (CFLAGS = ['-W', '-Wall']) # CXXFLAGS = $CFLAGS ... env.Append (CXXFLAGS = ['-W', '-Wall', '-Wconversion']) env.Append (LINKFLAGS = ['-Wl,--export-dynamic']) if env['verbose']: env['__verbose'] = ' --verbose' env['set__x'] = 'set -x;' config_hh = os.path.join (outdir, 'config.hh') version_hh = os.path.join (outdir, 'version.hh') env.Alias ('config', config_cache) ## Explicit target and dependencies if 'clean' in COMMAND_LINE_TARGETS: # ugh: prevent reconfigure instead of clean os.system ('touch %s' % config_cache) command = sys.argv[0] + ' -c .' sys.stdout.write ('Running %s ... ' % command) sys.stdout.write ('\n') s = os.system (command) if os.path.exists (config_cache): os.unlink (config_cache) Exit (s) if 'realclean' in COMMAND_LINE_TARGETS: command = 'rm -rf $(find . -name "out-scons" -o -name ".scon*")' sys.stdout.write ('Running %s ... ' % command) sys.stdout.write ('\n') s = os.system (command) if os.path.exists (config_cache): os.unlink (config_cache) Exit (s) # Without target arguments, build lily only if not COMMAND_LINE_TARGETS: env.Default ('lily') env.Alias ('all', '.') env.Alias ('doc', ['Documentation', 'Documentation/user', 'Documentation/topdocs']) env.Depends ('doc', ['lily', 'mf']) env.Depends ('input', ['lily', 'mf']) def list_sort (lst): sorted = lst sorted.sort () return sorted def configure (target, source, env): vre = re.compile ('^.*[^-.0-9]([0-9][0-9]*\.[0-9][.0-9]*).*$', re.DOTALL) def get_version (program): command = '(%(program)s --version || %(program)s -V) 2>&1' % vars () pipe = os.popen (command) output = pipe.read () if pipe.close (): return None v = re.sub (vre, '\\1', output) return string.split (v, '.') def test_program (lst, program, minimal, description, package): sys.stdout.write ('Checking %s version... ' % program) actual = get_version (program) if not actual: print 'not found' lst.append ((description, package, minimal, program, 'not installed')) return sys.stdout.write (string.join (actual, '.')) sys.stdout.write ('\n') if actual < string.split (minimal, '.'): lst.append ((description, package, minimal, program, string.join (actual, '.'))) required = [] test_program (required, 'gcc', '2.8', 'GNU C compiler', 'gcc') test_program (required, 'g++', '3.0.5', 'GNU C++ compiler', 'g++') test_program (required, 'python', '2.1', 'Python (www.python.org)', 'python') test_program (required, 'guile-config', '1.6', 'GUILE development', 'libguile-dev or guile-devel') # Do not use bison 1.50 and 1.75. test_program (required, 'bison', '1.25', 'Bison -- parser generator', 'bison') test_program (required, 'flex', '0.0', 'Flex -- lexer generator', 'flex') optional = [] test_program (optional, 'makeinfo', '4.7', 'Makeinfo tool', 'texinfo') test_program (optional, 'guile', '1.6', 'GUILE scheme', 'libguile-dev or guile-devel') test_program (optional, 'mftrace', '1.0.27', 'Metafont tracing Type1', 'mftrace') test_program (optional, 'perl', '4.0', 'Perl practical efficient readonly language', 'perl') #test_program (optional, 'foo', '2.0', 'Foomatic tester', 'bar') def CheckYYCurrentBuffer (context): context.Message ('Checking for yy_current_buffer... ') ret = conf.TryCompile ("""using namespace std; #include class yy_flex_lexer: public yyFlexLexer { public: yy_flex_lexer () { yy_current_buffer = 0; } };""", '.cc') context.Result (ret) return ret conf = Configure (env, custom_tests = { 'CheckYYCurrentBuffer' : CheckYYCurrentBuffer }) defines = { 'DIRSEP' : "'/'", 'PATHSEP' : "':'", 'TOPLEVEL_VERSION' : '"' + version + '"', 'PACKAGE': '"' + package.name + '"', 'DATADIR' : '"' + sharedir + '"', 'LILYPOND_DATADIR' : '"' + sharedir_package + '"', 'LOCAL_LILYPOND_DATADIR' : '"' + sharedir_package_version + '"', 'LOCALEDIR' : '"' + localedir + '"', } conf.env.Append (DEFINES = defines) command = r"""python -c 'import sys; sys.stdout.write ("%s/include/python%s" % (sys.prefix, sys.version[:3]))'""" #" PYTHON_INCLUDE = os.popen (command).read () env.Append (CPPPATH = PYTHON_INCLUDE) headers = ('sys/stat.h', 'assert.h', 'kpathsea/kpathsea.h', 'Python.h') for i in headers: if conf.CheckCHeader (i): key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i)) conf.env['DEFINES'][key] = 1 ccheaders = ('sstream',) for i in ccheaders: if conf.CheckCXXHeader (i): key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i)) conf.env['DEFINES'][key] = 1 functions = ('gettext', 'isinf', 'memmem', 'snprintf', 'vsnprintf') for i in functions: if 0 or conf.CheckFunc (i): key = re.sub ('[./]', '_', 'HAVE_' + string.upper (i)) conf.env['DEFINES'][key] = 1 if conf.CheckYYCurrentBuffer (): conf.env['DEFINES']['HAVE_FLEXLEXER_YY_CURRENT_BUFFER'] = 1 if conf.CheckLib ('dl'): pass if conf.CheckLib ('kpathsea'): conf.env['DEFINES']['KPATHSEA'] = 1 # huh? if conf.CheckLib ('kpathsea', 'kpse_find_file'): conf.env['DEFINES']['HAVE_KPSE_FIND_FILE'] = '1' if conf.CheckLib ('kpathsea', 'kpse_find_tfm'): conf.env['DEFINES']['HAVE_KPSE_FIND_TFM'] = '1' #this could happen after flower... env.ParseConfig ('guile-config compile') #this could happen only for compiling pango-* if env['gui']: env.ParseConfig ('pkg-config --cflags --libs gtk+-2.0') env.ParseConfig ('pkg-config --cflags --libs pango') if conf.CheckCHeader ('pango/pangofc-fontmap.h'): conf.env['DEFINES']['HAVE_PANGO_PANGOFC_FONTMAP_H'] = '1' if conf.CheckLib ('pango-1.0', 'pango_fc_font_map_add_decoder_find_func'): conf.env['DEFINES']['HAVE_PANGO_CVS'] = '1' conf.env['DEFINES']['HAVE_PANGO_FC_FONT_MAP_ADD_DECODER_FIND_FUNC'] = '1' if required: print print '********************************' print 'Please install required packages' for i in required: print '%s: %s-%s or newer (found: %s %s)' % i Exit (1) if optional: print print '*************************************' print 'Consider installing optional packages' for i in optional: print '%s: %s-%s or newer (found: %s %s)' % i return conf.Finish () def config_header (target, source, env): config = open (str (target[0]), 'w') for i in list_sort (env['DEFINES'].keys ()): config.write ('#define %s %s\n' % (i, env['DEFINES'][i])) config.close () env.Command (config_hh, config_cache, config_header) # hmm? def xuniquify (lst): n = [] for i in lst: if not i in n: n.append (i) lst = n return lst def uniquify (lst): d = {} n = len (lst) i = 0 while i < n: if not d.has_key (lst[i]): d[lst[i]] = 1 i += 1 else: del lst[i] n -= 1 return lst def save_config_cache (env): ## FIXME: Is this smart, using option cache for saving ## config.cache? I cannot seem to find the official method. for i in config_vars: if env.has_key (i) and type (env[i]) == type ([]): env[i] = uniquify (env[i]) opts.Save (config_cache, env) ## FIXME: Something changes in the ENVironment that triggers ## rebuild of everything if we continue after configuring. ## Maybe there's a variable missing from config_vars? ## How to print and debug the reasons scons has for rebuilding ## a target (make --debug)? ## heet van de naald: ## - Add a --debug=explain option that reports the reason(s) why SCons if 'config' in COMMAND_LINE_TARGETS: sys.stdout.write ('\n') sys.stdout.write ('LilyPond configured') sys.stdout.write ('\n') sys.stdout.write ('now run') sys.stdout.write ('\n') sys.stdout.write (' scons [TARGET|DIR]...') sys.stdout.write ('\n') Exit (0) elif 1: #not env['checksums']: command = sys.argv[0] + ' ' + string.join (COMMAND_LINE_TARGETS) sys.stdout.write ('Running %s ... ' % command) sys.stdout.write ('\n') s = os.system (command) Exit (s) if os.path.exists (config_cache) and 'config' in COMMAND_LINE_TARGETS: os.unlink (config_cache) # WTF? # scons: *** Calling Configure from Builders is not supported. # env.Command (config_cache, None, configure) if not os.path.exists (config_cache) \ or (os.stat ('SConstruct')[stat.ST_MTIME] > os.stat (config_cache)[stat.ST_MTIME]): env = configure (None, None, env) save_config_cache (env) env.Command (version_hh, '#/VERSION', '$PYTHON ./stepmake/bin/make-version.py VERSION > $TARGET') # post-config environment update env.Append ( absbuild = absbuild, run_prefix = run_prefix, LILYPONDPREFIX = os.path.join (run_prefix, 'share/lilypond'), LIBPATH = [os.path.join (absbuild, 'flower', env['out']),], CPPPATH = [outdir, '#',], LILYPOND_BIN = os.path.join (absbuild, 'lily', env['out'], 'lilypond-bin'), LILYPOND_BOOK_PATH = ['.', '#/input', '#/input/regression', '#/input/test', '#/input/tutorial', os.path.join (absbuild, 'mf', env['out']), '#/Documentation/user', os.path.join (absbuild, 'Documentation', env['out']), os.path.join (absbuild, 'Documentation/user', env['out']), ], MAKEINFO_PATH = ['.', '#/Documentation/user', os.path.join (absbuild, 'Documentation/user', env['out'])], ) Export ('env') SConscript ('buildscripts/builder.py') def symlink_tree (target, source, env): def mkdirs (dir): def mkdir (dir): if not dir: os.chdir (os.sep) return if not os.path.isdir (dir): if os.path.exists (dir): os.unlink (dir) os.mkdir (dir) os.chdir (dir) map (mkdir, string.split (dir, os.sep)) def symlink (src, dst): os.chdir (absbuild) dir = os.path.dirname (dst) mkdirs (dir) if src[0] == '#': frm = os.path.join (srcdir, src[1:]) else: depth = len (string.split (dir, '/')) if src.find ('@') > -1: frm = os.path.join ('../' * depth, string.replace (src, '@', env['out'])) else: frm = os.path.join ('../' * depth, src, env['out']) if src[-1] == '/': frm = os.path.join (frm, os.path.basename (dst)) if env['verbose']: print 'ln -s %s -> %s' % (frm, os.path.basename (dst)) os.symlink (frm, os.path.basename (dst)) shutil.rmtree (run_prefix) prefix = os.path.join (env['out'], 'usr') map (lambda x: symlink (x[0], os.path.join (prefix, x[1])), # ^# := source dir # @ := out # /$ := add dst file_name (('python', 'lib/lilypond/python'), ('lily/', 'bin/lilypond-bin'), ('scripts/', 'bin/lilypond'), ('scripts/', 'bin/lilypond-book'), ('#mf', 'share/lilypond/fonts/mf'), ('mf', 'share/lilypond/fonts/afm'), ('mf', 'share/lilypond/fonts/tfm'), ('mf', 'share/lilypond/fonts/type1'), ('#tex', 'share/lilypond/tex/source'), ('mf', 'share/lilypond/tex/generate'), ('#ly', 'share/lilypond/ly'), ('#scm', 'share/lilypond/scm'), ('#ps', 'share/lilypond/ps'), ('po/@/nl.mo', 'share/locale/nl/LC_MESSAGES/lilypond.mo'), ('elisp', 'share/lilypond/elisp'))) os.chdir (srcdir) if env['debugging']: stamp = os.path.join (run_prefix, 'stamp') env.Command (stamp, 'SConstruct', [symlink_tree, 'touch $TARGET']) env.Depends ('lily', stamp) #### dist, tar def plus (a, b): a + b def cvs_entry_is_dir (line): return line[0] == 'D' and line[-2] == '/' def cvs_entry_is_file (line): return line[0] == '/' and line[-2] == '/' def cvs_dirs (dir): ENTRIES = os.path.join (dir, 'CVS/Entries') if not os.path.exists (ENTRIES): return [] entries = open (ENTRIES).readlines () dir_entries = filter (cvs_entry_is_dir, entries) dirs = map (lambda x: os.path.join (dir, x[2:x[2:].index ('/')+3]), dir_entries) return dirs + map (cvs_dirs, dirs) def cvs_files (dir): ENTRIES = os.path.join (dir, 'CVS/Entries') entries = open (ENTRIES).readlines () file_entries = filter (cvs_entry_is_file, entries) files = map (lambda x: x[1:x[1:].index ('/')+1], file_entries) return map (lambda x: os.path.join (dir, x), files) #subdirs = reduce (lambda x, y: x + y, cvs_dirs ('.')) #print `subdirs` readme_files = ['AUTHORS', 'README', 'INSTALL', 'NEWS'] foo = map (lambda x: env.TXT (x + '.txt', os.path.join ('Documentation/topdocs', x)), readme_files) txt_files = map (lambda x: x + '.txt', readme_files) src_files = reduce (lambda x, y: x + y, map (cvs_files, subdirs)) tar_base = package.name + '-' + version tar_name = tar_base + '.tar.gz' ball_prefix = os.path.join (outdir, tar_base) tar_ball = os.path.join (outdir, tar_name) dist_files = src_files + txt_files ball_files = map (lambda x: os.path.join (ball_prefix, x), dist_files) map (lambda x: env.Depends (tar_ball, x), ball_files) map (lambda x: env.Command (os.path.join (ball_prefix, x), x, 'ln $SOURCE $TARGET'), dist_files) tar = env.Command (tar_ball, src_files, 'tar czf $TARGET -C $TARGET.dir %s' % tar_base) env.Alias ('tar', tar) dist_ball = os.path.join (package.release_dir, tar_name) env.Command (dist_ball, tar_ball, 'if [ -e $SOURCE -a -e $TARGET ]; then rm $TARGET; fi;' \ + 'ln $SOURCE $TARGET') env.Depends ('dist', dist_ball) patch_name = os.path.join (outdir, tar_base + '.diff.gz') patch = env.PATCH (patch_name, tar_ball) env.Depends (patch_name, dist_ball) env.Alias ('release', patch) env.Append ( ETAGSFLAGS = ["""--regex='{c++}/^LY_DEFINE *(\([^,]+\)/\1/'""", """--regex='{c++}/^LY_DEFINE *([^"]*"\([^"]+\)"/\1/'"""]) # filter-out some files? env.Command ('TAGS', src_files, 'etags $ETAGSFLAGS $SOURCES') for d in subdirs: if os.path.exists (os.path.join (d, 'SConscript')): b = os.path.join (env['build'], d, env['out']) # Support clean sourcetree build (--srcdir build) # and ./out build. if os.path.abspath (b) != os.path.abspath (d): env.BuildDir (b, d, duplicate = 0) SConscript (os.path.join (b, 'SConscript'))