forked from midifi/midifi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fbuildroot.py
463 lines (390 loc) · 15.8 KB
/
fbuildroot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from fbuild.builders.cxx import guess_static, guess_shared
from fbuild.builders.c.msvc import Builder as MsvcBuilder
from fbuild.builders.pkg_config import PkgConfig
from fbuild.builders.file import copy
from fbuild.builders.c import Library
from fbuild.builders import find_program
from fbuild.record import Record
from fbuild.path import Path
import fbuild
import sys, re, textwrap, os, shutil, tarfile, urllib.request,\
xml.etree.ElementTree as etree
from optparse import make_option
def pre_options(parser):
group = parser.add_option_group('config options')
group.add_options((
make_option('--flx', help='Use the given Felix compiler'),
make_option('--flxflag', help='Pass the given flag to flx',
action='append', default=[]),
make_option('--cxxflag', help='Pass the given flag to the C++ compiler',
action='append', default=[]),
make_option('--release', help='Build a release build',
action='store_true'),
))
def check_fluid(linker):
fluidsynth = Path(linker.prefix + 'fluidsynth' + linker.suffix)
fluidsynth = fluidsynth.addroot(Path('fluidsynth') / 'fluidsynth' / 'src')
message = textwrap.dedent('''
You need to build Fluidsynth separately first!
Try runnung 'cd fluidsynth/fluidsynth; cmake'.
(See http://sourceforge.net/p/fluidsynth/wiki/BuildingWithCMake/ for info.)
'''.rstrip().lstrip('\n')).replace('\n', ' ', 1)
if not fluidsynth.exists():
raise fbuild.ConfigFailed(message)
return fluidsynth
def make_lib_args(cxx, path):
if isinstance(cxx, MsvcBuilder):
return path + '.lib'
else:
return '-l' + path
def get_info_for(ctx, cxx, pkg, defaults):
try:
pkgconfig = PkgConfig(ctx, pkg)
cflags = pkgconfig.cflags()
libs = pkgconfig.libs()
if isinstance(cxx, MsvcBuilder):
for lib in libs.split():
if lib.startswith('-l'):
libs = libs.replace(lib, '')
except:
ctx.logger.check('trying to get libs for %s' % pkg)
cflags = []
libs = ' '.join(map(lambda l: make_lib_args(cxx, l),
defaults.get(pkg, [])))
failmsg = 'failed'
if libs:
failmsg += ' (using defaults %s)' % libs
ctx.logger.failed(failmsg)
else:
ctx.logger.check('trying to get libs for -%s' % pkg)
ctx.logger.passed('ok %s' % libs)
return cflags, libs
def write_fpc(ctx, fpc, write):
directory = ctx.buildroot / 'config'
directory.makedirs()
ctx.logger.check(' * generating fpc', directory / fpc, color='yellow')
write(directory)
@fbuild.db.caches
def gen_sfml_fpc(ctx, cxx):
sys.path.insert(0, str(Path(__file__).dirname() / 'sfml'))
import gen_fpc
default_libs = {
'sfml-system': ['sfml-system'],
'sfml-window': ['sfml-window', 'sfml-system'],
'sfml-graphics': ['sfml-graphics', 'sfml-window', 'sfml-system'],
}
if not ctx.options.release:
default_libs = {name: map(lambda s: s+'-d', libs) \
for name, libs in default_libs.items()}
all_libs = {}
for pkg in gen_fpc.packages:
all_libs[pkg] = get_info_for(ctx, cxx, 'sfml-' + pkg, default_libs)[1]
for pkg, libs in all_libs.items():
write_fpc(ctx, 'sfml-%s.fpc' % pkg, lambda d: gen_fpc.write(pkg, libs, d))
@fbuild.db.caches
def gen_midifile_fpc(ctx, cxx):
def write(directory):
with open('midifile-flx/midifile.fpc') as base_f:
base = base_f.read()
# XXX: This is an ugly hack!
fpc = base.replace('lib: -lmidifile',
'lib: -L%s %s' % (ctx.buildroot,
make_lib_args(cxx, 'midifile')))
fpc = fpc.replace('provides_dlib',
'cflags: -Imidifile/include\nprovides_dlib')
with open(directory / 'midifile.fpc', 'w') as f:
f.write(fpc)
write_fpc(ctx, 'midifile.fpc', write)
@fbuild.db.caches
def gen_fluid_fpc(ctx, cxx):
all_flags = '-I%s ' % ctx.buildroot.abspath()
all_libs = '-L%s ' % ctx.buildroot.abspath()
for pkg in 'glib-2.0', 'gthread-2.0':
cflags, libs = get_info_for(ctx, cxx, pkg, {})
all_flags += ' '.join(cflags) + ' '
all_libs += libs + ' '
all_libs += make_lib_args(cxx, 'fluidsynth')
fluidsynth_root = Path('fluidsynth') / 'fluidsynth'
fluidsynth_includes = ['include', 'src/midi', 'src/utils']
for include in fluidsynth_includes:
all_flags += ' -I' + str(fluidsynth_root / include)
def write(directory):
template = textwrap.dedent('''
Name: fluid
Description: Midifi fluidsynth stuff!
cflags: {flags}
provides_dlib: {libs}
provides_slib: {libs}
'''.lstrip('\n').rstrip(' '))
fpc = template.format(flags=all_flags, libs=all_libs)
with open(directory / 'fluid.fpc', 'w') as f:
f.write(fpc)
write_fpc(ctx, 'fluid.fpc', write)
def gen_fpc(*args):
gen_sfml_fpc(*args)
gen_midifile_fpc(*args)
gen_fluid_fpc(*args)
class Felix(fbuild.db.PersistentObject):
def __init__(self, ctx, flx=None, flx_pkgconfig=None, debug=False,
optimize=False, flags=[]):
self.flx = find_program(ctx, [flx or 'flx'])
self.ctx = ctx
self.debug = debug
self.optimize = optimize
self.flags = flags
self._test()
def _test(self):
self.ctx.logger.check('checking flx')
failed = False
with self.tempfile("println 'Hello, world!';") as f:
try:
output = self.uncached_run(f, quieter=1)
except:
self.ctx.logger.failed()
raise
else:
if output[1] or not output[0].rstrip().endswith(b'Hello, world!'):
self.ctx.logger.failed()
raise fbuild.ConfigFailed(
'flx test program did not give correct output')
else:
self.ctx.logger.passed()
def uncached_compile(self, dst, src, cxx, includes=[], pkgconfig_paths=[],
libpaths=[], libs=[], cflags=[]):
dst = Path(dst).addroot(self.ctx.buildroot)
dst = dst.replaceext(cxx.exe_linker.suffix)
new_libs = []
def process_library(lib):
if isinstance(lib, Library):
for sublib in lib.libs:
process_library(sublib)
new_libs.extend(lib.external_libs)
libpaths.append(lib.dirname())
slib = str(lib.basename().replaceext(''))
if slib.startswith('lib') and lib.splitext()[1] == '.a':
slib = slib[3:]
new_libs.append(slib)
libpaths.extend(lib.libpaths)
for lib in libs:
process_library(lib)
cmd = [self.flx, '-c', '--static']
if self.debug:
cmd.append('--debug')
if self.optimize:
cmd.append('--usage=hyperlight')
cmd.extend(('-o', dst))
cmd.extend('--pkgconfig-path+=' + path for path in pkgconfig_paths)
cmd.extend('-I' + include for include in includes)
cmd.extend('-L' + path for path in libpaths)
cmd.extend('-l' + lib for lib in new_libs)
cmd.extend('--cflags=' + flag for flag in cflags)
cmd.extend(self.flags)
cmd.append(src)
self.ctx.execute(cmd, 'flx', '%s -> %s' % (src, dst), color='link')
return dst
@fbuild.db.cachemethod
def compile(self, dst, src: fbuild.db.SRC, others: fbuild.db.SRCS, *args,
**kw) -> fbuild.db.DST:
return self.uncached_compile(dst, src, *args, **kw)
def uncached_run(self, path, *args, **kw):
return self.ctx.execute([self.flx, path], *args, **kw)
def tempfile(self, code):
return fbuild.temp.tempfile(code, '.flx')
@fbuild.db.cacheproperty
def platform_extra(self):
get_toolchain = '''
include 'std/felix/toolchain_clang_config';
include 'std/felix/toolchain_interface';
include 'std/felix/flx_pkgconfig';
config_dirs := #Config::std_config.FLX_CONFIG_DIRS;
pkgconfig := FlxPkgConfig::FlxPkgConfigQuery config_dirs;
toolchain := pkgconfig.getpkgfield1 ('toolchain', 'toolchain');
for arg in #System::args perform
if arg.startswith('--toolchain=') perform
toolchain = arg.[12 to];
println toolchain;
'''
with self.tempfile(get_toolchain) as f:
self.ctx.logger.check('detecting toolchain used by flx '\
'(this may take a while!)')
try:
toolchain = self.uncached_run(f, quieter=1)[0]
except:
self.ctx.logger.failed()
raise fbuild.ConfigFailed('could not detect flx toolchain')
else:
toolchain = toolchain.decode('utf-8').strip().split('\n')[-1]
self.ctx.logger.passed('ok %s' % toolchain)
if 'msvc' in toolchain:
return {'windows'}
elif 'gcc' in toolchain:
return {'gcc'}
elif 'clang' in toolchain:
return {'clang'}
else:
raise fbuild.ConfigFailed('unknown toolchain %s' % toolchain)
@fbuild.db.caches
def configure(ctx, release):
config_kw = {}
wcxxflags = []
if release:
config_kw['optimize'] = True
wcxxflags.append('/MT')
else:
config_kw['debug'] = True
wcxxflags.append('/LDd')
felix = Felix(ctx, flags=ctx.options.flxflag, **config_kw)
extra = felix.platform_extra
kw = dict(platform_extra=extra, platform_options=[
({'windows'}, {'flags+': ['/EHsc']+wcxxflags}),
({'posix'}, {'flags+': ['-std=c++11']}),
], flags=ctx.options.cxxflag, **config_kw)
static = guess_static(ctx, **kw)
shared = guess_shared(ctx, **kw)
gen_fpc(ctx, static)
linker = static.lib_linker if 'windows' in extra else shared.lib_linker
fluidsynth = check_fluid(linker)
return Record(static=static, shared=shared, felix=felix, fluidsynth=fluidsynth)
#--------------------------------------------------------------------------------
# BUILDING.
#--------------------------------------------------------------------------------
@fbuild.db.caches
def download_soundfont(ctx) -> fbuild.db.DST:
ctx.logger.check('downloading FluidR3 GM (this will take a few moments)')
tar = ctx.buildroot / 'fluid-soundfont.tar.gz'
url = 'http://www.musescore.org/download/fluid-soundfont.tar.gz'
try:
with urllib.request.urlopen(url) as response, open(tar, 'wb') as f:
shutil.copyfileobj(response, f)
except:
ctx.logger.failed()
raise
else:
ctx.logger.passed('ok %s' % tar)
return tar
@fbuild.db.caches
def extract_soundfont(ctx, tar: fbuild.db.SRC) -> fbuild.db.DST:
dst = ctx.buildroot / 'data' / 'FluidR3_GM.sf2'
dst.dirname().makedirs()
member = 'FluidR3 GM2-2.SF2'
ctx.logger.check('extracting FluidR3 GM (this will take a few moments)')
try:
with tarfile.open(str(tar)) as tf:
tf.extract(member, ctx.buildroot)
os.rename(ctx.buildroot / member, dst)
except FileExistsError:
pass
except:
ctx.logger.failed()
raise
else:
ctx.logger.passed('ok %s' % dst)
return dst
def get_soundfont(ctx):
tar = download_soundfont(ctx)
extract_soundfont(ctx, tar)
@fbuild.db.caches
def find_font(ctx) -> fbuild.db.DST:
ctx.logger.check('locating arial font')
font = None
if sys.platform == 'win32':
font = Path(os.environ['SYSTEMROOT']) / 'Fonts' / 'Arial.ttf'
if not font.exists():
font = None
elif sys.platform.startswith('linux'):
# Check /etc/fonts/fonts.conf.
font_dirs = []
fonts = Path('/etc/fonts/fonts.conf')
if not fonts.exists():
ctx.logger.failed()
raise fbuild.ConfigFailed('cannot locate fonts.conf')
tree = etree.parse(str(fonts))
for element in tree.findall('dir'):
path = Path(element.text)
if element.attrib.get('prefix') == 'xdg' and \
'XDG_DATA_HOME' in os.environ:
path = path.addroot(os.environ['XDG_DATA_HOME'])
try:
font = Path(next(path.find('Arial.ttf', include_dirs=False)))
except StopIteration:
pass
else:
break
if font is None:
ctx.logger.failed()
raise fbuild.ConfigFailed('cannot locate arial font')
else:
ctx.logger.passed('ok %s' % font)
return font
@fbuild.db.caches
def save_font(ctx, font: fbuild.db.SRC) -> fbuild.db.DST:
dst = ctx.buildroot / 'data' / 'Arial.ttf'
return copy(ctx, font, dst)
def get_font(ctx):
font = find_font(ctx)
save_font(ctx, font)
@fbuild.db.caches
def save_exports(ctx, lib: fbuild.db.SRC) -> fbuild.db.DST:
dst = ctx.buildroot / 'fluidsynth.def'
ctx.logger.check(' * extracting exports', dst, color='yellow')
output, _ = ctx.execute(['dumpbin', '/nologo', '/exports', lib], quieter=1)
output = output.decode('ascii')
export_res = re.search(r'name(.*)Summary', output, re.DOTALL | re.MULTILINE)
exports = ['EXPORTS']
exports.extend(map(str.strip, export_res.group(1).strip().splitlines()))
exports.extend(['new_fluid_timer', 'delete_fluid_timer', 'new_fluid_track',
'fluid_track_add_event', 'fluid_player_add_track',
'fluid_player_callback'])
with open(dst, 'w') as f:
f.write('\n'.join(exports))
# Create the lib file.
return dst
@fbuild.db.caches
def make_lib(ctx, exports: fbuild.db.SRC, linker, fluid) -> fbuild.db.DST:
dst = ctx.buildroot / fluid.basename()
cmd = [linker.exe, '/nologo', '/def:' + exports, '/OUT:' + dst]
ctx.execute(cmd, str(linker), '%s -> %s' % (exports, dst), color='link')
return dst
def copy_dll2(ctx, fluid: fbuild.db.SRC) -> fbuild.db.DST:
dst = ctx.buildroot / fluid.basename()
copy(ctx, fluid, dst)
return dst
def copy_dll(ctx, fluid):
dll = fluid.replaceext('.dll')
if not dll.exists():
url = 'https://github.com/midifi/midifi/blob/master/README.md#windows-1'
raise fbuild.ConfigFailed('cannot find %s\nsee %s for more info' % (dll,
url))
return copy_dll2(ctx, dll)
def build_midifile(ctx, rec):
if isinstance(rec.shared, MsvcBuilder):
builder = rec.shared
lflags = ['/DEBUG'] if not ctx.options.release else []
else:
builder = rec.static
lflags = []
return builder.build_lib('midifile',
Path.glob('midifile/src-library/*.cpp'), includes=['midifile/include'],
lflags=lflags)
def build_midifi(ctx, rec, midifile):
all_sources = []
for pat in 'midifi/*.flx', 'sfml/sfml/*.flx', 'midifile-flx/midifile.flx':
all_sources.extend(Path.glob(pat))
rec.felix.compile('midifi', 'midifi.flx', all_sources, rec.static,
includes=['sfml', 'midifile', 'midifile-flx'],
pkgconfig_paths=['build/config'], libs=[midifile])
def build(ctx):
rec = configure(ctx, ctx.options.release)
get_soundfont(ctx)
get_font(ctx)
if isinstance(rec.static, MsvcBuilder):
exports = save_exports(ctx, rec.fluidsynth)
make_lib(ctx, exports, rec.static.lib_linker, rec.fluidsynth)
copy_dll(ctx, rec.fluidsynth)
else:
copy(ctx, rec.fluidsynth, ctx.buildroot / rec.fluidsynth.basename())
midifile = build_midifile(ctx, rec)
build_midifi(ctx, rec, midifile)