Stdin, stdout and stderr updated, tested.
[Faustine.git] / interpreter / preprocessor / faust-0.9.47mr3 / tools / scbuilder / scbuilder
1 #!/usr/bin/env ruby
2 #
3 # File: scbuilder
4 # Contents: Build script for SuperCollider plugins
5 # Authors: Stefan Kersten <sk AT k-hornz DOT de>
6 # nescivi AT gmail DOT com
7 #
8 # This is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2, or (at your option) any later version.
11 #
12 # This software is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc., 675
19 # Mass Ave, Cambridge, MA 02139, USA.
20 #
21 # ======================================================================
22 # Usage:
23 #
24 # This script compiles `simple' SuperCollider plugins, i.e. plugins that
25 # consist of only one source file. Source files may be C++ implementations or
26 # a Faust[1] DSP specification.
27 #
28 # Source file paths are passed either in a file (specified with the
29 # PLUGIN_SOURCES option) or via standard input.
30 #
31 # E.g., when source file paths are in a file called sources.txt:
32 #
33 # $ sc-build-plugins PLUGIN_SOURCES=sources.txt
34 #
35 # Alternatively, source file paths can be passed via standard input:
36 #
37 # $ my-command | sc-build-plugins
38 #
39 # ======================================================================
40
41 # ======================================================================
42 # Bootstrap (ruby): Execute scons with content after __END__
43
44 require 'tempfile'
45
46 Tempfile.open("r") { |f|
47 f << DATA.read
48 f.flush
49 system("scons", "-f", f.path, *ARGV)
50 }
51
52 __END__
53
54 # ======================================================================
55 # NOTE: Please use an indentation level of 4 spaces, i.e. no mixing of
56 # tabs and spaces.
57 # ======================================================================
58
59 # ======================================================================
60 # setup
61 # ======================================================================
62
63 EnsureSConsVersion(0,96)
64 EnsurePythonVersion(2,3)
65 SConsignFile()
66
67 # ======================================================================
68 # imports
69 # ======================================================================
70
71 import glob
72 import os
73 import re
74 import sys
75 import subprocess
76 import types
77 import tarfile
78
79 # ======================================================================
80 # constants
81 # ======================================================================
82
83 PACKAGE = 'SuperCollider'
84
85 def short_cpu_name(cpu):
86 if cpu == 'Power Macintosh':
87 cpu = 'ppc'
88 return cpu.lower()
89
90 PLATFORM = os.uname()[0].lower()
91 CPU = short_cpu_name(os.uname()[4])
92
93 if PLATFORM == 'darwin':
94 PLATFORM_SYMBOL = 'SC_DARWIN'
95 PLUGIN_EXT = '.scx'
96 DEFAULT_PREFIX = '/'
97 elif PLATFORM == 'freebsd':
98 PLATFORM_SYMBOL = 'SC_FREEBSD'
99 PLUGIN_EXT = '.so'
100 DEFAULT_PREFIX = '/usr/local'
101 elif PLATFORM == 'linux':
102 PLATFORM_SYMBOL = 'SC_LINUX'
103 PLUGIN_EXT = '.so'
104 DEFAULT_PREFIX = '/usr/local'
105 elif PLATFORM == 'windows':
106 PLATFORM_SYMBOL = 'SC_WIN32'
107 PLUGIN_EXT = '.scx'
108 DEFAULT_PREFIX = '/'
109 else:
110 print 'Unknown platform: %s' % PLATFORM
111 Exit(1)
112
113 if CPU == 'ppc':
114 DEFAULT_OPT_ARCH = '7450'
115 elif CPU in [ 'i586', 'i686' ]:
116 # FIXME: better detection
117 DEFAULT_OPT_ARCH = CPU
118 else:
119 DEFAULT_OPT_ARCH = None
120
121 # ======================================================================
122 # util
123 # ======================================================================
124
125 def make_os_env(*keys):
126 env = os.environ
127 res = {}
128 for key in keys:
129 if env.has_key(key):
130 res[key] = env[key]
131 return res
132
133 def CheckPKGConfig(context, version):
134 context.Message( 'Checking for pkg-config... ' )
135 ret = context.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0]
136 context.Result( ret )
137 return ret
138
139 def CheckPKG(context, name):
140 context.Message('Checking for %s... ' % name)
141 ret = context.TryAction('pkg-config --exists \'%s\'' % name)[0]
142 res = None
143 if ret:
144 res = Environment(ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'))
145 res.ParseConfig('pkg-config --cflags --libs \'%s\'' % name)
146 res['PKGCONFIG'] = name
147 context.Result(ret)
148 return (ret, res)
149
150 def get_new_pkg_env():
151 return Environment(ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'))
152
153 def merge_lib_info(env, *others):
154 for other in others:
155 env.AppendUnique(CCFLAGS = other.get('CCFLAGS', []))
156 env.AppendUnique(CPPDEFINES = other.get('CPPDEFINES', []))
157 env.AppendUnique(CPPPATH = other.get('CPPPATH', []))
158 env.AppendUnique(CXXFLAGS = other.get('CXXFLAGS', []))
159 env.AppendUnique(LIBS = other.get('LIBS', []))
160 env.AppendUnique(LIBPATH = other.get('LIBPATH', []))
161 env['LINKFLAGS'] = env['LINKFLAGS'] + other.get('LINKFLAGS', "")
162
163 def flatten_dir(dir):
164 res = []
165 for root, dirs, files in os.walk(dir):
166 if 'CVS' in dirs: dirs.remove('CVS')
167 if '.svn' in dirs: dirs.remove('.svn')
168 for f in files:
169 res.append(os.path.join(root, f))
170 return res
171
172 def install_dir(env, src_dir, dst_dir, filter_re, strip_levels=0):
173 nodes = []
174 for root, dirs, files in os.walk(src_dir):
175 src_paths = []
176 dst_paths = []
177 if 'CVS' in dirs: dirs.remove('CVS')
178 if '.svn' in dirs: dirs.remove('.svn')
179 for d in dirs[:]:
180 if filter_re.match(d):
181 src_paths += flatten_dir(os.path.join(root, d))
182 dirs.remove(d)
183 for f in files:
184 if filter_re.match(f):
185 src_paths.append(os.path.join(root, f))
186 dst_paths += map(
187 lambda f:
188 os.path.join(
189 dst_dir,
190 *f.split(os.path.sep)[strip_levels:]),
191 src_paths)
192 nodes += env.InstallAs(dst_paths, src_paths)
193 return nodes
194
195 def is_installing():
196 pat = re.compile('^install.*$')
197 for x in COMMAND_LINE_TARGETS:
198 if pat.match(x): return True
199 return False
200
201 def bin_dir(prefix):
202 return os.path.join(prefix, 'bin')
203 def lib_dir(prefix):
204 return os.path.join(prefix, 'lib')
205
206 def pkg_data_dir(prefix, *args):
207 if PLATFORM == 'darwin':
208 base = os.path.join(prefix, 'Library/Application Support')
209 else:
210 base = os.path.join(prefix, 'share')
211 return os.path.join(base, PACKAGE, *args)
212 def pkg_doc_dir(prefix, *args):
213 if PLATFORM == 'darwin':
214 base = os.path.join(prefix, 'Library/Documentation')
215 else:
216 base = os.path.join(prefix, 'share', 'doc')
217 return os.path.join(base, PACKAGE, *args)
218 def pkg_include_dir(prefix, *args):
219 return os.path.join(prefix, 'include', PACKAGE, *args)
220 def pkg_lib_dir(prefix, *args):
221 return os.path.join(lib_dir(prefix), PACKAGE, *args)
222
223 def pkg_classlib_dir(prefix, *args):
224 return pkg_data_dir(prefix, 'SCClassLibrary', *args)
225 def pkg_extension_dir(prefix, *args):
226 return pkg_data_dir(prefix, 'Extensions', *args)
227
228 def make_opt_flags(env):
229 flags = [
230 "-O3",
231 ## "-fomit-frame-pointer", # can behave strangely for sclang
232 "-ffast-math",
233 "-fstrength-reduce"
234 ]
235 arch = env.get('OPT_ARCH')
236 if arch:
237 if CPU == 'ppc':
238 flags.extend([ "-mcpu=%s" % (arch,) ])
239 else:
240 flags.extend([ "-march=%s" % (arch,) ])
241 if CPU == 'ppc':
242 flags.extend([ "-fsigned-char", "-mhard-float",
243 ## "-mpowerpc-gpopt", # crashes sqrt
244 "-mpowerpc-gfxopt"
245 ])
246 return flags
247
248 # ======================================================================
249 # Faust builder
250 # ======================================================================
251
252 def faustInitEnvironment(env):
253 dsp = Builder(
254 action = 'faust -a supercollider.cpp -o $TARGET $SOURCE',
255 suffix = '.cpp',
256 src_suffix = '.dsp')
257 xml = Builder(
258 action = ['faust -o /dev/null -xml $SOURCE', Move('$TARGET', '${SOURCE}.xml')],
259 suffix = '.dsp.xml',
260 src_suffix = '.dsp')
261 sc = Builder(
262 action = '$FAUST2SC --prefix="$FAUST2SC_PREFIX" -o $TARGET $SOURCE',
263 suffix = '.sc',
264 src_suffix = '.dsp.xml')
265 env.Append(BUILDERS = { 'Faust' : dsp,
266 'FaustXML' : xml,
267 'FaustSC' : sc })
268
269 # ======================================================================
270 # Command line options
271 # ======================================================================
272
273 opts = Options('scache.conf', ARGUMENTS)
274 opts.AddOptions(
275 BoolOption('ALTIVEC',
276 'Build with Altivec support', 1),
277 PathOption('BUILD_DIR',
278 'Directory to build temporary files in', '.',
279 PathOption.PathIsDirCreate),
280 BoolOption('BUILD_SC', 'Build SuperCollider class files', 1),
281 BoolOption('BUILD_XML', 'Build Faust XML files', 1),
282 ('CC', 'C compiler executable'),
283 ('CCFLAGS', 'C compiler flags'),
284 ('CXX', 'C++ compiler executable'),
285 ('CXXFLAGS', 'C++ compiler flags'),
286 BoolOption('CROSSCOMPILE',
287 'Crosscompile for another platform (does not do SSE support check)', 0),
288 BoolOption('DEBUG',
289 'Build with debugging information', 0),
290 PathOption('DESTDIR',
291 'Intermediate installation prefix for packaging', '/'),
292 PathOption('FAUST2SC',
293 'Path to faust2sc script', 'faust2sc',
294 PathOption.PathAccept),
295 ( 'FAUST2SC_PREFIX',
296 'Prefix for SC classes generated from Faust definitions', ''),
297 PathOption('PREFIX',
298 'Installation prefix', DEFAULT_PREFIX),
299 PathOption('INSTALLDIR',
300 'Installation directory', '',
301 PathOption.PathAccept),
302 BoolOption('SSE',
303 'Build with SSE support', 1),
304 ( 'OPT_ARCH',
305 'Architecture to optimize for', DEFAULT_OPT_ARCH),
306 PathOption('PLUGIN_SOURCES',
307 'File containing plugin sources (C++ or Faust)', None,
308 PathOption.PathAccept),
309 PathOption('SC_SOURCE_DIR',
310 'SuperCollider source directory', '../supercollider',
311 PathOption.PathAccept),
312 )
313
314 if PLATFORM == 'darwin':
315 opts.AddOptions(
316 BoolOption('UNIVERSAL',
317 'Build universal binaries (see UNIVERSAL_ARCHS)', 1),
318 ListOption('UNIVERSAL_ARCHS',
319 'Architectures to build for',
320 'all', ['ppc', 'i386'])
321 )
322
323 # ======================================================================
324 # basic environment
325 # ======================================================================
326
327 env = Environment(options = opts,
328 ENV = make_os_env('PATH', 'PKG_CONFIG_PATH'),
329 PACKAGE = PACKAGE,
330 URL = 'http://supercollider.sourceforge.net')
331 env.Append(PATH = ['/usr/local/bin', '/usr/bin', '/bin'])
332 faustInitEnvironment(env)
333
334 # checks for DISTCC and CCACHE as used in modern linux-distros:
335
336 if os.path.exists('/usr/lib/distcc/bin'):
337 os.environ['PATH'] = '/usr/lib/distcc/bin:' + os.environ['PATH']
338 env['ENV']['DISTCC_HOSTS'] = os.environ['DISTCC_HOSTS']
339
340 if os.path.exists('/usr/lib/ccache/bin'):
341 os.environ['PATH'] = '/usr/lib/ccache/bin:' + os.environ['PATH']
342 env['ENV']['CCACHE_DIR'] = os.environ['CCACHE_DIR']
343
344 env['ENV']['PATH'] = os.environ['PATH']
345 env['ENV']['HOME'] = os.environ['HOME']
346
347 # ======================================================================
348 # installation directories
349 # ======================================================================
350
351 FINAL_PREFIX = '$PREFIX'
352 INSTALL_PREFIX = os.path.join('$DESTDIR', '$PREFIX')
353
354 # ======================================================================
355 # configuration
356 # ======================================================================
357
358 def make_conf(env):
359 return Configure(env, custom_tests = { 'CheckPKGConfig' : CheckPKGConfig,
360 'CheckPKG' : CheckPKG })
361
362 def isDefaultBuild():
363 return not env.GetOption('clean') and not 'debian' in COMMAND_LINE_TARGETS
364
365 conf = make_conf(env)
366
367 # libraries
368 libraries = { }
369 features = { }
370
371 if isDefaultBuild():
372 if not conf.CheckPKGConfig('0'):
373 print 'pkg-config not found.'
374 Exit(1)
375
376 # SC includes and flags
377 success, libraries['scplugin'] = conf.CheckPKG('libscplugin')
378 if not success:
379 success, libraries['scplugin'] = conf.CheckPKG('libscsynth')
380 if success:
381 libraries['scplugin']['LIBS'] = []
382 else:
383 src_dir = env['SC_SOURCE_DIR']
384 if os.path.isdir(src_dir):
385 libraries['scplugin'] = Environment(
386 CPPDEFINES = [PLATFORM_SYMBOL],
387 CPPPATH = [os.path.join(src_dir, 'Headers/common'),
388 os.path.join(src_dir, 'Headers/plugin_interface'),
389 os.path.join(src_dir, 'Headers/server')])
390 else:
391 print "Please specify the SC_SOURCE_DIR option."
392 Exit(1)
393 else:
394 libraries['scplugin'] = Environment()
395
396 # only _one_ Configure context can be alive at a time
397 env = conf.Finish()
398
399 # altivec
400 if env['ALTIVEC']:
401 if PLATFORM == 'darwin':
402 altivec_flags = [ '-faltivec' ]
403 else:
404 altivec_flags = [ '-maltivec', '-mabi=altivec' ]
405 libraries['altivec'] = env.Copy()
406 libraries['altivec'].Append(
407 CCFLAGS = altivec_flags,
408 CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 16)])
409 altiConf = Configure(libraries['altivec'])
410 features['altivec'] = altiConf.CheckCHeader('altivec.h')
411 altiConf.Finish()
412 else:
413 features['altivec'] = False
414
415 # sse
416 if env['SSE']:
417 libraries['sse'] = env.Copy()
418 libraries['sse'].Append(
419 CCFLAGS = ['-msse', '-mfpmath=sse'],
420 CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 16)])
421 sseConf = Configure(libraries['sse'])
422 hasSSEHeader = sseConf.CheckCHeader('xmmintrin.h')
423 if env['CROSSCOMPILE']:
424 build_host_supports_sse = True
425 print 'NOTICE: cross compiling for another platform: assuming SSE support'
426 else:
427 build_host_supports_sse = False
428 if CPU != 'ppc':
429 if PLATFORM == 'freebsd':
430 machine_info = os.popen ("sysctl -a hw.instruction_sse").read()[:-1]
431 x86_flags = 'no'
432 if "1" in [x for x in machine_info]:
433 build_host_supports_sse = True
434 x86_flags = 'sse'
435 elif PLATFORM != 'darwin':
436 flag_line = os.popen ("cat /proc/cpuinfo | grep '^flags'").read()[:-1]
437 x86_flags = flag_line.split (": ")[1:][0].split ()
438 else:
439 machine_info = os.popen ("sysctl -a machdep.cpu").read()[:-1]
440 x86_flags = machine_info.split()
441 if "sse" in [x.lower() for x in x86_flags]:
442 build_host_supports_sse = True
443 print 'NOTICE: CPU has SSE support'
444 else:
445 print 'NOTICE: CPU does not have SSE support'
446 features['sse'] = hasSSEHeader and build_host_supports_sse
447 sseConf.Finish()
448 else:
449 features['sse'] = False
450
451 opts.Save('scache.conf', env)
452 Help(opts.GenerateHelpText(env))
453
454 # defines and compiler flags
455 env.Append(
456 CPPDEFINES = [ '_REENTRANT' ],
457 CCFLAGS = [ '-Wno-unknown-pragmas' ],
458 CXXFLAGS = [ '-Wno-deprecated' ]
459 )
460
461 # debugging flags
462 if env['DEBUG']:
463 env.Append(CCFLAGS = '-g')
464 else:
465 env.Append(
466 CCFLAGS = make_opt_flags(env),
467 CPPDEFINES = ['NDEBUG'])
468
469 # platform specific
470 if PLATFORM == 'darwin':
471 env.Append(
472 CCFLAGS = '-fvisibility=hidden'
473 )
474
475 # vectorization
476 if features['altivec']:
477 merge_lib_info(env, libraries['altivec'])
478 elif features['sse']:
479 merge_lib_info(env, libraries['sse'])
480 else:
481 env.AppendUnique(CPPDEFINES = [('SC_MEMORY_ALIGNMENT', 1)])
482
483 # ======================================================================
484 # Source/plugins
485 # ======================================================================
486
487 pluginEnv = env.Copy(
488 SHLIBPREFIX = '',
489 SHLIBSUFFIX = PLUGIN_EXT)
490 if PLATFORM == 'darwin':
491 pluginEnv['SHLINKFLAGS'] = '$LINKFLAGS -bundle'
492 merge_lib_info(pluginEnv, libraries['scplugin'])
493
494 def get_sources(env):
495 if env.has_key('PLUGIN_SOURCES') and env['PLUGIN_SOURCES']:
496 fd = open(env['PLUGIN_SOURCES'], "r")
497 sources = fd.readlines()
498 fd.close()
499 else:
500 print "Reading PLUGIN_SOURCES from <stdin>"
501 sources = sys.stdin.readlines()
502 return map(lambda x: x.strip(), sources)
503
504 def make_build_file(env, path, ext=""):
505 newpath = os.path.splitext(os.path.basename(path))[0] + ext
506 return os.path.join(env['BUILD_DIR'], newpath)
507
508 def make_plugin_target(env, path):
509 name, ext = os.path.splitext(path)
510 if ext == ".dsp":
511 src = env.Faust(make_build_file(env, path), path)
512 else:
513 src = path
514 return env.SharedLibrary(make_build_file(env, path), src)
515
516 def make_xml_target(env, path):
517 name, ext = os.path.splitext(path)
518 if ext == '.dsp':
519 if env['BUILD_SC']:
520 xml = env.FaustXML(make_build_file(env, path), path)
521 sc = env.FaustSC(make_build_file(env, path), xml)
522 if env['BUILD_XML']:
523 return [sc, xml]
524 else:
525 return sc
526 elif env['BUILD_XML']:
527 return env.FaustXML(make_build_file(env, path), path)
528 return []
529
530 sources = get_sources(env)
531 plugins = map(lambda x: make_plugin_target(pluginEnv, x), sources)
532 xml = map(lambda x: make_xml_target(pluginEnv, x), sources)
533
534 # ======================================================================
535 # installation
536 # ======================================================================
537
538 env.Alias('install', env.Install(
539 os.path.join(pkg_extension_dir(INSTALL_PREFIX), env['INSTALLDIR']),
540 plugins + xml))
541
542 # ======================================================================
543 # cleanup
544 # ======================================================================
545
546 env.Clean('scrub',
547 Split('config.log scache.conf .sconf_temp .sconsign.dblite'))
548
549 # ======================================================================
550 # configuration summary
551 # ======================================================================
552
553 def yesorno(p):
554 if p: return 'yes'
555 else: return 'no'
556
557 print '------------------------------------------------------------------------'
558 print ' ALTIVEC: %s' % yesorno(features['altivec'])
559 print ' BUILD_SC: %s' % yesorno(env['BUILD_SC'])
560 print ' BUILD_XML: %s' % yesorno(env['BUILD_XML'])
561 print ' FAUST2SC: %s' % env['FAUST2SC']
562 if env['INSTALLDIR']:
563 print ' INSTALLDIR: %s' % env['INSTALLDIR']
564 print ' DEBUG: %s' % yesorno(env['DEBUG'])
565 if env.has_key('PLUGIN_SOURCES'):
566 print ' PLUGIN_SOURCES: %s' % env['PLUGIN_SOURCES']
567 else:
568 print ' PLUGIN_SOURCES: %s' % 'stdin'
569 print ' PREFIX: %s' % env['PREFIX']
570 print ' SC_SOURCE_DIR %s' % env['SC_SOURCE_DIR']
571 print ' SSE: %s' % yesorno(features['sse'])
572 print ' CROSSCOMPILE: %s' % yesorno(env['CROSSCOMPILE'])
573 print '------------------------------------------------------------------------'
574
575 # ======================================================================
576 # EOF