--- /dev/null
+#!/usr/bin/env ruby
+#
+# File: scbuilder
+# Contents: Build script for SuperCollider plugins
+# Authors: Stefan Kersten <sk AT k-hornz DOT de>
+# nescivi AT gmail DOT com
+#
+# This is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2, or (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 675
+# Mass Ave, Cambridge, MA 02139, USA.
+#
+# ======================================================================
+# Usage:
+#
+# This script compiles `simple' SuperCollider plugins, i.e. plugins that
+# consist of only one source file. Source files may be C++ implementations or
+# a Faust[1] DSP specification.
+#
+# Source file paths are passed either in a file (specified with the
+# PLUGIN_SOURCES option) or via standard input.
+#
+# E.g., when source file paths are in a file called sources.txt:
+#
+# $ sc-build-plugins PLUGIN_SOURCES=sources.txt
+#
+# Alternatively, source file paths can be passed via standard input:
+#
+# $ my-command | sc-build-plugins
+#
+# ======================================================================
+
+# ======================================================================
+# Bootstrap (ruby): Execute scons with content after __END__
+
+require 'tempfile'
+
+Tempfile.open("r") { |f|
+ f << DATA.read
+ f.flush
+ system("scons", "-f", f.path, *ARGV)
+}
+
+__END__
+
+# ======================================================================
+# NOTE: Please use an indentation level of 4 spaces, i.e. no mixing of
+# tabs and spaces.
+# ======================================================================
+
+# ======================================================================
+# setup
+# ======================================================================
+
+EnsureSConsVersion(0,96)
+EnsurePythonVersion(2,3)
+SConsignFile()
+
+# ======================================================================
+# imports
+# ======================================================================
+
+import glob
+import os
+import re
+import sys
+import subprocess
+import types
+import tarfile
+
+# ======================================================================
+# constants
+# ======================================================================
+
+PACKAGE = 'SuperCollider'
+
+def short_cpu_name(cpu):
+ if cpu == 'Power Macintosh':
+ cpu = 'ppc'
+ return cpu.lower()
+
+PLATFORM = os.uname()[0].lower()
+CPU = short_cpu_name(os.uname()[4])
+
+if PLATFORM == 'darwin':
+ PLATFORM_SYMBOL = 'SC_DARWIN'
+ PLUGIN_EXT = '.scx'
+ DEFAULT_PREFIX = '/'
+elif PLATFORM == 'freebsd':
+ PLATFORM_SYMBOL = 'SC_FREEBSD'
+ PLUGIN_EXT = '.so'
+ DEFAULT_PREFIX = '/usr/local'
+elif PLATFORM == 'linux':
+ PLATFORM_SYMBOL = 'SC_LINUX'
+ PLUGIN_EXT = '.so'
+ DEFAULT_PREFIX = '/usr/local'
+elif PLATFORM == 'windows':
+ PLATFORM_SYMBOL = 'SC_WIN32'
+ PLUGIN_EXT = '.scx'
+ DEFAULT_PREFIX = '/'
+else:
+ print 'Unknown platform: %s' % PLATFORM
+ Exit(1)
+
+if CPU == 'ppc':
+ DEFAULT_OPT_ARCH = '7450'
+elif CPU in [ 'i586', 'i686' ]:
+ # FIXME: better detection
+ DEFAULT_OPT_ARCH = CPU
+else:
+ DEFAULT_OPT_ARCH = None
+
+# ======================================================================
+# util
+# ======================================================================
+
+def make_os_env(*keys):
+ env = os.environ
+ res = {}
+ for key in keys:
+ if env.has_key(key):
+ res[key] = env[key]
+ return res
+
+def CheckPKGConfig(context, version):
+ context.Message( 'Checking for pkg-config... ' )
+ ret = context.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
+ context.Result( ret )
+ return ret
+
+def CheckPKG(context, name):
+ context.Message('Checking for %s... ' % name)
+ ret = context.TryAction('pkg-config --exists \'%s\'' % name)[0]
+ res = None
+ if ret:
+ res = Environment(ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'))
+ res.ParseConfig('pkg-config --cflags --libs \'%s\'' % name)
+ res['PKGCONFIG'] = name
+ context.Result(ret)
+ return (ret, res)
+
+def get_new_pkg_env():
+ return Environment(ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'))
+
+def merge_lib_info(env, *others):
+ for other in others:
+ env.AppendUnique(CCFLAGS = other.get('CCFLAGS', []))
+ env.AppendUnique(CPPDEFINES = other.get('CPPDEFINES', []))
+ env.AppendUnique(CPPPATH = other.get('CPPPATH', []))
+ env.AppendUnique(CXXFLAGS = other.get('CXXFLAGS', []))
+ env.AppendUnique(LIBS = other.get('LIBS', []))
+ env.AppendUnique(LIBPATH = other.get('LIBPATH', []))
+ env['LINKFLAGS'] = env['LINKFLAGS'] + other.get('LINKFLAGS', "")
+
+def flatten_dir(dir):
+ res = []
+ for root, dirs, files in os.walk(dir):
+ if 'CVS' in dirs: dirs.remove('CVS')
+ if '.svn' in dirs: dirs.remove('.svn')
+ for f in files:
+ res.append(os.path.join(root, f))
+ return res
+
+def install_dir(env, src_dir, dst_dir, filter_re, strip_levels=0):
+ nodes = []
+ for root, dirs, files in os.walk(src_dir):
+ src_paths = []
+ dst_paths = []
+ if 'CVS' in dirs: dirs.remove('CVS')
+ if '.svn' in dirs: dirs.remove('.svn')
+ for d in dirs[:]:
+ if filter_re.match(d):
+ src_paths += flatten_dir(os.path.join(root, d))
+ dirs.remove(d)
+ for f in files:
+ if filter_re.match(f):
+ src_paths.append(os.path.join(root, f))
+ dst_paths += map(
+ lambda f:
+ os.path.join(
+ dst_dir,
+ *f.split(os.path.sep)[strip_levels:]),
+ src_paths)
+ nodes += env.InstallAs(dst_paths, src_paths)
+ return nodes
+
+def is_installing():
+ pat = re.compile('^install.*$')
+ for x in COMMAND_LINE_TARGETS:
+ if pat.match(x): return True
+ return False
+
+def bin_dir(prefix):
+ return os.path.join(prefix, 'bin')
+def lib_dir(prefix):
+ return os.path.join(prefix, 'lib')
+
+def pkg_data_dir(prefix, *args):
+ if PLATFORM == 'darwin':
+ base = os.path.join(prefix, 'Library/Application Support')
+ else:
+ base = os.path.join(prefix, 'share')
+ return os.path.join(base, PACKAGE, *args)
+def pkg_doc_dir(prefix, *args):
+ if PLATFORM == 'darwin':
+ base = os.path.join(prefix, 'Library/Documentation')
+ else:
+ base = os.path.join(prefix, 'share', 'doc')
+ return os.path.join(base, PACKAGE, *args)
+def pkg_include_dir(prefix, *args):
+ return os.path.join(prefix, 'include', PACKAGE, *args)
+def pkg_lib_dir(prefix, *args):
+ return os.path.join(lib_dir(prefix), PACKAGE, *args)
+
+def pkg_classlib_dir(prefix, *args):
+ return pkg_data_dir(prefix, 'SCClassLibrary', *args)
+def pkg_extension_dir(prefix, *args):
+ return pkg_data_dir(prefix, 'Extensions', *args)
+
+def make_opt_flags(env):
+ flags = [
+ "-O3",
+ ## "-fomit-frame-pointer", # can behave strangely for sclang
+ "-ffast-math",
+ "-fstrength-reduce"
+ ]
+ arch = env.get('OPT_ARCH')
+ if arch:
+ if CPU == 'ppc':
+ flags.extend([ "-mcpu=%s" % (arch,) ])
+ else:
+ flags.extend([ "-march=%s" % (arch,) ])
+ if CPU == 'ppc':
+ flags.extend([ "-fsigned-char", "-mhard-float",
+ ## "-mpowerpc-gpopt", # crashes sqrt
+ "-mpowerpc-gfxopt"
+ ])
+ return flags
+
+# ======================================================================
+# Faust builder
+# ======================================================================
+
+def faustInitEnvironment(env):
+ dsp = Builder(
+ action = 'faust -a supercollider.cpp -o $TARGET $SOURCE',
+ suffix = '.cpp',
+ src_suffix = '.dsp')
+ xml = Builder(
+ action = ['faust -o /dev/null -xml $SOURCE', Move('$TARGET', '${SOURCE}.xml')],
+ suffix = '.dsp.xml',
+ src_suffix = '.dsp')
+ sc = Builder(
+ action = '$FAUST2SC --prefix="$FAUST2SC_PREFIX" -o $TARGET $SOURCE',
+ suffix = '.sc',
+ src_suffix = '.dsp.xml')
+ env.Append(BUILDERS = { 'Faust' : dsp,
+ 'FaustXML' : xml,
+ 'FaustSC' : sc })
+
+# ======================================================================
+# Command line options
+# ======================================================================
+
+opts = Options('scache.conf', ARGUMENTS)
+opts.AddOptions(
+ BoolOption('ALTIVEC',
+ 'Build with Altivec support', 1),
+ PathOption('BUILD_DIR',
+ 'Directory to build temporary files in', '.',
+ PathOption.PathIsDirCreate),
+ BoolOption('BUILD_SC', 'Build SuperCollider class files', 1),
+ BoolOption('BUILD_XML', 'Build Faust XML files', 1),
+ ('CC', 'C compiler executable'),
+ ('CCFLAGS', 'C compiler flags'),
+ ('CXX', 'C++ compiler executable'),
+ ('CXXFLAGS', 'C++ compiler flags'),
+ BoolOption('CROSSCOMPILE',
+ 'Crosscompile for another platform (does not do SSE support check)', 0),
+ BoolOption('DEBUG',
+ 'Build with debugging information', 0),
+ PathOption('DESTDIR',
+ 'Intermediate installation prefix for packaging', '/'),
+ PathOption('FAUST2SC',
+ 'Path to faust2sc script', 'faust2sc',
+ PathOption.PathAccept),
+ ( 'FAUST2SC_PREFIX',
+ 'Prefix for SC classes generated from Faust definitions', ''),
+ PathOption('PREFIX',
+ 'Installation prefix', DEFAULT_PREFIX),
+ PathOption('INSTALLDIR',
+ 'Installation directory', '',
+ PathOption.PathAccept),
+ BoolOption('SSE',
+ 'Build with SSE support', 1),
+ ( 'OPT_ARCH',
+ 'Architecture to optimize for', DEFAULT_OPT_ARCH),
+ PathOption('PLUGIN_SOURCES',
+ 'File containing plugin sources (C++ or Faust)', None,
+ PathOption.PathAccept),
+ PathOption('SC_SOURCE_DIR',
+ 'SuperCollider source directory', '../supercollider',
+ PathOption.PathAccept),
+ )
+
+if PLATFORM == 'darwin':
+ opts.AddOptions(
+ BoolOption('UNIVERSAL',
+ 'Build universal binaries (see UNIVERSAL_ARCHS)', 1),
+ ListOption('UNIVERSAL_ARCHS',
+ 'Architectures to build for',
+ 'all', ['ppc', 'i386'])
+ )
+
+# ======================================================================
+# basic environment
+# ======================================================================
+
+env = Environment(options = opts,
+ ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'),
+ PACKAGE = PACKAGE,
+ URL = 'http://supercollider.sourceforge.net')
+env.Append(PATH = ['/usr/local/bin', '/usr/bin', '/bin'])
+faustInitEnvironment(env)
+
+# checks for DISTCC and CCACHE as used in modern linux-distros:
+
+if os.path.exists('/usr/lib/distcc/bin'):
+ os.environ['PATH'] = '/usr/lib/distcc/bin:' + os.environ['PATH']
+ env['ENV']['DISTCC_HOSTS'] = os.environ['DISTCC_HOSTS']
+
+if os.path.exists('/usr/lib/ccache/bin'):
+ os.environ['PATH'] = '/usr/lib/ccache/bin:' + os.environ['PATH']
+ env['ENV']['CCACHE_DIR'] = os.environ['CCACHE_DIR']
+
+env['ENV']['PATH'] = os.environ['PATH']
+env['ENV']['HOME'] = os.environ['HOME']
+
+# ======================================================================
+# installation directories
+# ======================================================================
+
+FINAL_PREFIX = '$PREFIX'
+INSTALL_PREFIX = os.path.join('$DESTDIR', '$PREFIX')
+
+# ======================================================================
+# configuration
+# ======================================================================
+
+def make_conf(env):
+ return Configure(env, custom_tests = { 'CheckPKGConfig' : CheckPKGConfig,
+ 'CheckPKG' : CheckPKG })
+
+def isDefaultBuild():
+ return not env.GetOption('clean') and not 'debian' in COMMAND_LINE_TARGETS
+
+conf = make_conf(env)
+
+# libraries
+libraries = { }
+features = { }
+
+if isDefaultBuild():
+ if not conf.CheckPKGConfig('0'):
+ print 'pkg-config not found.'
+ Exit(1)
+
+ # SC includes and flags
+ success, libraries['scplugin'] = conf.CheckPKG('libscplugin')
+ if not success:
+ success, libraries['scplugin'] = conf.CheckPKG('libscsynth')
+ if success:
+ libraries['scplugin']['LIBS'] = []
+ else:
+ src_dir = env['SC_SOURCE_DIR']
+ if os.path.isdir(src_dir):
+ libraries['scplugin'] = Environment(
+ CPPDEFINES = [PLATFORM_SYMBOL],
+ CPPPATH = [os.path.join(src_dir, 'Headers/common'),
+ os.path.join(src_dir, 'Headers/plugin_interface'),
+ os.path.join(src_dir, 'Headers/server')])
+ else:
+ print "Please specify the SC_SOURCE_DIR option."
+ Exit(1)
+else:
+ libraries['scplugin'] = Environment()
+
+# only _one_ Configure context can be alive at a time
+env = conf.Finish()
+
+# altivec
+if env['ALTIVEC']:
+ if PLATFORM == 'darwin':
+ altivec_flags = [ '-faltivec' ]
+ else:
+ altivec_flags = [ '-maltivec', '-mabi=altivec' ]
+ libraries['altivec'] = env.Copy()
+ libraries['altivec'].Append(
+ CCFLAGS = altivec_flags,
+ CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 16)])
+ altiConf = Configure(libraries['altivec'])
+ features['altivec'] = altiConf.CheckCHeader('altivec.h')
+ altiConf.Finish()
+else:
+ features['altivec'] = False
+
+# sse
+if env['SSE']:
+ libraries['sse'] = env.Copy()
+ libraries['sse'].Append(
+ CCFLAGS = ['-msse', '-mfpmath=sse'],
+ CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 16)])
+ sseConf = Configure(libraries['sse'])
+ hasSSEHeader = sseConf.CheckCHeader('xmmintrin.h')
+ if env['CROSSCOMPILE']:
+ build_host_supports_sse = True
+ print 'NOTICE: cross compiling for another platform: assuming SSE support'
+ else:
+ build_host_supports_sse = False
+ if CPU != 'ppc':
+ if PLATFORM == 'freebsd':
+ machine_info = os.popen ("sysctl -a hw.instruction_sse").read()[:-1]
+ x86_flags = 'no'
+ if "1" in [x for x in machine_info]:
+ build_host_supports_sse = True
+ x86_flags = 'sse'
+ elif PLATFORM != 'darwin':
+ flag_line = os.popen ("cat /proc/cpuinfo | grep '^flags'").read()[:-1]
+ x86_flags = flag_line.split (": ")[1:][0].split ()
+ else:
+ machine_info = os.popen ("sysctl -a machdep.cpu").read()[:-1]
+ x86_flags = machine_info.split()
+ if "sse" in [x.lower() for x in x86_flags]:
+ build_host_supports_sse = True
+ print 'NOTICE: CPU has SSE support'
+ else:
+ print 'NOTICE: CPU does not have SSE support'
+ features['sse'] = hasSSEHeader and build_host_supports_sse
+ sseConf.Finish()
+else:
+ features['sse'] = False
+
+opts.Save('scache.conf', env)
+Help(opts.GenerateHelpText(env))
+
+# defines and compiler flags
+env.Append(
+ CPPDEFINES = [ '_REENTRANT' ],
+ CCFLAGS = [ '-Wno-unknown-pragmas' ],
+ CXXFLAGS = [ '-Wno-deprecated' ]
+ )
+
+# debugging flags
+if env['DEBUG']:
+ env.Append(CCFLAGS = '-g')
+else:
+ env.Append(
+ CCFLAGS = make_opt_flags(env),
+ CPPDEFINES = ['NDEBUG'])
+
+# platform specific
+if PLATFORM == 'darwin':
+ env.Append(
+ CCFLAGS = '-fvisibility=hidden'
+ )
+
+# vectorization
+if features['altivec']:
+ merge_lib_info(env, libraries['altivec'])
+elif features['sse']:
+ merge_lib_info(env, libraries['sse'])
+else:
+ env.AppendUnique(CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 1)])
+
+# ======================================================================
+# Source/plugins
+# ======================================================================
+
+pluginEnv = env.Copy(
+ SHLIBPREFIX = '',
+ SHLIBSUFFIX = PLUGIN_EXT)
+if PLATFORM == 'darwin':
+ pluginEnv['SHLINKFLAGS'] = '$LINKFLAGS -bundle'
+merge_lib_info(pluginEnv, libraries['scplugin'])
+
+def get_sources(env):
+ if env.has_key('PLUGIN_SOURCES') and env['PLUGIN_SOURCES']:
+ fd = open(env['PLUGIN_SOURCES'], "r")
+ sources = fd.readlines()
+ fd.close()
+ else:
+ print "Reading PLUGIN_SOURCES from <stdin>"
+ sources = sys.stdin.readlines()
+ return map(lambda x: x.strip(), sources)
+
+def make_build_file(env, path, ext=""):
+ newpath = os.path.splitext(os.path.basename(path))[0] + ext
+ return os.path.join(env['BUILD_DIR'], newpath)
+
+def make_plugin_target(env, path):
+ name, ext = os.path.splitext(path)
+ if ext == ".dsp":
+ src = env.Faust(make_build_file(env, path), path)
+ else:
+ src = path
+ return env.SharedLibrary(make_build_file(env, path), src)
+
+def make_xml_target(env, path):
+ name, ext = os.path.splitext(path)
+ if ext == '.dsp':
+ if env['BUILD_SC']:
+ xml = env.FaustXML(make_build_file(env, path), path)
+ sc = env.FaustSC(make_build_file(env, path), xml)
+ if env['BUILD_XML']:
+ return [sc, xml]
+ else:
+ return sc
+ elif env['BUILD_XML']:
+ return env.FaustXML(make_build_file(env, path), path)
+ return []
+
+sources = get_sources(env)
+plugins = map(lambda x: make_plugin_target(pluginEnv, x), sources)
+xml = map(lambda x: make_xml_target(pluginEnv, x), sources)
+
+# ======================================================================
+# installation
+# ======================================================================
+
+env.Alias('install', env.Install(
+ os.path.join(pkg_extension_dir(INSTALL_PREFIX), env['INSTALLDIR']),
+ plugins + xml))
+
+# ======================================================================
+# cleanup
+# ======================================================================
+
+env.Clean('scrub',
+ Split('config.log scache.conf .sconf_temp .sconsign.dblite'))
+
+# ======================================================================
+# configuration summary
+# ======================================================================
+
+def yesorno(p):
+ if p: return 'yes'
+ else: return 'no'
+
+print '------------------------------------------------------------------------'
+print ' ALTIVEC: %s' % yesorno(features['altivec'])
+print ' BUILD_SC: %s' % yesorno(env['BUILD_SC'])
+print ' BUILD_XML: %s' % yesorno(env['BUILD_XML'])
+print ' FAUST2SC: %s' % env['FAUST2SC']
+if env['INSTALLDIR']:
+ print ' INSTALLDIR: %s' % env['INSTALLDIR']
+print ' DEBUG: %s' % yesorno(env['DEBUG'])
+if env.has_key('PLUGIN_SOURCES'):
+ print ' PLUGIN_SOURCES: %s' % env['PLUGIN_SOURCES']
+else:
+ print ' PLUGIN_SOURCES: %s' % 'stdin'
+print ' PREFIX: %s' % env['PREFIX']
+print ' SC_SOURCE_DIR %s' % env['SC_SOURCE_DIR']
+print ' SSE: %s' % yesorno(features['sse'])
+print ' CROSSCOMPILE: %s' % yesorno(env['CROSSCOMPILE'])
+print '------------------------------------------------------------------------'
+
+# ======================================================================
+# EOF