forked from mixxxdj/mixxx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SConscript
1157 lines (995 loc) · 53.6 KB
/
SConscript
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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import os
import SCons
import shutil
import subprocess
import time
import datetime
import glob
import uuid
from xml.dom import minidom
import SCons.Script as SCons
from build import util, depends
mixxx_version = util.get_mixxx_version()
branch_name = util.get_branch_name()
vcs_revision = util.get_revision()
vcs_name = util.get_current_vcs()
print("WE ARE IN:", os.getcwd())
print("Building ", branch_name, " - rev.", vcs_revision)
plugins = []
# Grab these from the SConstruct above us
Import('build')
Import('sources')
env = build.env
flags = build.flags
# Make a static library of all Mixxx's sources. This library will be linked into
# both mixxx and mixxx-test.
mixxx_lib = env.StaticLibrary('libmixxx',
[source for source in sources
if str(source) != 'src/main.cpp'])
# mixxx.qrc must not be bundled into libmixxx.a since the linker will not link
# it into the resulting binary unless it is on the link command-line explicitly
# (it has no link-time symbols that are needed by anything in Mixxx).
mixxx_qrc = env.StaticObject(env.Qrc5('res/mixxx.cc', 'res/mixxx.qrc'))
# libmixxx.a needs to precede all other libraries so that symbols it requires
# end up in the linker's list of unresolved symbols before other libraries are
# searched for symbols.
env.Prepend(LIBS=mixxx_lib)
mixxx_main = env.StaticObject('src/main.cpp')
#Tell SCons to build Mixxx
#=========================
if build.platform_is_windows:
dist_dir = 'dist%s' % build.bitwidth
# Populate the stuff that changes in the .rc file
fo = open(File('src/mixxx.rc.include').abspath, "w")
str_list = []
str_list.append('#define VER_FILEVERSION ')
# Remove anything after ~ or - in the version number and replace the dots with commas
str_list.append(mixxx_version.partition('~')[0].partition('-')[0].replace('.',','))
if vcs_revision:
str_list.append(','+str(vcs_revision))
str_list.append('\n')
str_list.append('#define VER_PRODUCTVERSION ')
str_list.append(mixxx_version.partition('~')[0].partition('-')[0].replace('.',','))
if vcs_revision:
str_list.append(','+str(vcs_revision))
str_list.append('\n')
import datetime
now = datetime.datetime.now()
str_list.append('#define CUR_YEAR "'+str(now.year)+'"\n\n')
if build.build_is_debug:
str_list.append('#define DEBUG 1\n')
if 'pre' in mixxx_version.lower():
str_list.append('#define PRERELEASE 1\n')
fo.write(''.join(str_list))
fo.close()
mixxx_rc = env.RES('src/mixxx.rc')
mixxx_bin = env.Program('mixxx',
[mixxx_main, mixxx_qrc, mixxx_rc],
LINKCOM = [env['LINKCOM'], 'mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;1'])
elif build.platform_is_osx:
# Bug #1258435: executable name must match CFBundleExecutable in the
# Info.plist. For codesigned bundles it seems the CFBundleExecutable
# must match the bundle name or else we SIGILL at startup (not sure
# why).
mixxx_bin = env.Program('Mixxx', [mixxx_main, mixxx_qrc])
else:
mixxx_bin = env.Program('mixxx', [mixxx_main, mixxx_qrc])
# For convenience, copy the Mixxx binary out of the build directory to the
# root. Don't do it on windows because the binary can't run on its own and needs
# the DLLs present with it.
if not build.platform_is_windows:
copy_mixxx_bin = Command("../mixxx", mixxx_bin, Copy("$TARGET", "$SOURCE"))
Default(copy_mixxx_bin)
else:
Default(mixxx_bin)
test_bin = None
def define_test_targets(default=False):
global test_bin
test_files = Glob('src/test/*.cpp', strings=True)
test_env = env.Clone()
test_env.Append(CPPPATH="lib/googletest-1.8.x/googletest/include")
test_env.Append(LIBPATH="lib/googletest-1.8.x/googletest")
test_env.Append(LIBS=['gtest'])
test_env.Append(CPPPATH="lib/googletest-1.8.x/googlemock/include")
test_env.Append(LIBPATH="lib/googletest-1.8.x/googlemock")
test_env.Append(LIBS=['gmock'])
test_env.Append(CPPPATH="lib/benchmark/include")
test_env.Append(LIBPATH="lib/benchmark")
test_env.Append(LIBS=['benchmark'])
test_files = [test_env.StaticObject(filename)
if filename !='src/test/main.cpp' else filename
for filename in test_files]
if build.platform_is_windows:
# For SHGetValueA in Google's benchmark library.
test_env.Append(LIBS=['Shlwapi'])
# We want a terminal for tests.
if build.toolchain_is_msvs:
test_env['LINKFLAGS'].remove('/subsystem:windows,6.01')
test_env['LINKFLAGS'].append('/subsystem:console,6.01')
elif build.toolchain_is_gnu:
test_env['LINKFLAGS'].remove('--subsystem,windows')
test_env['LINKFLAGS'].append('--subsystem,console')
# Currently both executables are built with /subsystem:windows
# and the console is attached manually
test_bin = test_env.Program(
'mixxx-test', [test_files, mixxx_qrc, mixxx_rc],
LINKCOM = [env['LINKCOM'], 'mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;1'])
else:
test_bin = test_env.Program('mixxx-test', [test_files, mixxx_qrc])
if not build.platform_is_windows:
copy_test_bin = Command("../mixxx-test", test_bin, Copy("$TARGET", "$SOURCE"))
env.Alias('mixxx-test', copy_test_bin)
# Running mixxx-test via a Command is hacky because it expects a
# target. Using the source '../mixxx-test' makes the Command
# depend on the Copy.
run_test = Command('mixxx-test-results', '../mixxx-test', './mixxx-test')
env.Alias('test', run_test)
if default:
Default(copy_test_bin)
else:
env.Alias('mixxx-test', test_bin)
if default:
Default(test_bin)
# If the 'test' flag is 1, then build the mixxx-test target by default. If
# 'test' is in the target list then run mixxx-test.
build_tests_by_default = int(build.flags['test']) != 0
build_tests = 'mixxx-test' in COMMAND_LINE_TARGETS
run_tests = 'test' in COMMAND_LINE_TARGETS
if build_tests or run_tests or build_tests_by_default:
define_test_targets(default=build_tests_by_default)
def construct_version(build, mixxx_version, branch_name, vcs_revision):
if branch_name.startswith('release-'):
branch_name = branch_name.replace('release-', '')
# Include build type in the filename.
build_type = 'release' if build.build_is_release else 'debug'
# New, simpler logic: mixxx version, branch name, git revision,
# release/build. Example: mixxx-1.12.0-master-gitXXXX-release
return "%s-%s-%s%s-%s" % (mixxx_version, branch_name, vcs_name,
vcs_revision, build_type)
def ubuntu_construct_version(build, mixxx_version, branch_name, vcs_revision,
ubuntu_version, distro_version):
# The format of a Debian/Ubuntu version is:
#
# [epoch:]upstream_version[-debian_revision]
#
# A detailed description of the valid characters and sorting order of
# versions can be found here:
# https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
#
# For package upgrades to work correctly, we want the following
# orderings on package versions:
#
# nightly build < pre-alpha < alpha < beta < rc1 < rc2 < final release
#
# The sorting rules are complicated but the key detail is: "The lexical
# comparison is a comparison of ASCII values modified so that all the
# letters sort earlier than all the non-letters and so that a tilde
# sorts before anything, even the end of a part."
#
# The Mixxx version stored in src/defs_version.h (the "mixxx_version"
# parameter to this function) is formatted like:
#
# Pre Alpha: 2.0.0-alpha-pre
# Alpha: 2.0.0-alpha
# Beta: 2.0.0-beta
# RC: 2.0.0-rc1
# Final: 2.0.0
#
# Since hyphens are a separator character between the upstream version
# and Debian version, we replace these with tildes.
#
# Other goals:
# - We would like to know the branch and commit of a package.
# - We would like the PPA to trump the official Debian package.
#
# The following versions are sorted from low to high order:
# 1.9.9
# 2.0.0~alpha~pre
# 2.0.0~alpha
# 2.0.0~beta~pre
# 2.0.0~beta
# 2.0.0~dfsg4 <- official Debian package version
# 2.0.0~rc1
# 2.0.0
# 2.0.1~alpha~pre
#
# Our official Debian packages have a ~dfsg section, so in this case an
# rc1 package in our PPA would trump an official Debian package
# (probably not what we want but not too bad since we would probably
# publish a "2.0.0" final to our PPA before the official Debian package
# is even released.
#
# Note in the above sorted list that if the branch name were included
# after the mixxx_version, 2.0.0~master would sort earlier than
# 2.0.0~rc1~master! To prevent branch and revision tags from
# interfering with package ordering we include them in the
# debian_revision portion of the version. This ensures they are only
# used for sorting if the upstream version of two packages is identical.
upstream_version = mixxx_version.replace('-', '~')
assert '_' not in upstream_version
# Strip underscores and dashes in the branch name.
branch_name = branch_name.strip('_-')
assert branch_name and branch_name != '(no branch)'
return "%s-%s~%s~%s%s~%s" % (upstream_version, ubuntu_version, branch_name,
vcs_name, vcs_revision, distro_version)
#Set up the install target
#=========================
#Mixxx binary
binary_files = [mixxx_bin];
if test_bin is not None:
binary_files.append(test_bin)
if build.bundle_pdbs:
binary_files.append(env.SideEffect('mixxx.pdb', mixxx_bin))
#Skins
skin_files = Glob('#res/skins/*')
#Controller mappings
controllermappings_files = Glob('#res/controllers/*')
# Translation files
translation_files = Glob('#res/translations/*.qm') + Glob(os.path.join(build.env['QTDIR'], 'translations/qt_*.qm'))
# Font files
font_files = Glob('#res/fonts/*')
#Keyboard mapping(s)
keyboardmappings_files = Glob('#res/keyboard/*')
#Documentation
docs_files = Glob('#./LICENSE')
docs_files += Glob('#./README')
docs_files += Glob('#./Mixxx-Manual.pdf')
#.desktop file for KDE/GNOME menu
dotdesktop_files = Glob('#res/linux/mixxx.desktop')
#.appdata.xml file for KDE/GNOME AppStream iniative
dotappstream_files = Glob('#res/linux/mixxx.appdata.xml')
#udev rule file for USB HID and Bulk controllers
hidudev_files = Glob('#res/linux/mixxx-usb-uaccess.rules')
#Icon file for menu entry
icon_files = Glob('#res/images/mixxx_icon.svg')
#Images for preferences dialog
image_files = Glob('#res/images/preferences/*') # These are compiled in to the "mixxx" binary through mixxx.qrc
#Windows DLLs
dll_files = []
if build.toolchain_is_msvs and not build.static_dependencies:
# skip the MSVC DLLs in case they're in there too
dll_files.extend(Glob('%s/*.dll' % build.winlib_path))
dll_files.extend(Glob('%s/lib/*.dll' % build.winlib_path))
if build.bundle_pdbs:
dll_files.extend(Glob('%s/*.pdb' % build.winlib_path))
dll_files.extend(Glob('%s/lib/*.pdb' % build.winlib_path))
elif build.crosscompile and build.platform_is_windows and build.toolchain_is_gnu and not build.static_dependencies:
# We're cross-compiling, grab these from the crosscompile bin
# folder. How should we be doing this?
dll_files = Glob('#/../../mixxx-win%slib-crossmingw' % build.bitwidth)
qt_modules = depends.Qt.enabled_modules(build)
if build.platform_is_windows:
suffix = 'd.dll' if build.build_is_debug else '.dll'
if not build.static_qt:
qt_modules = ['$QTDIR/lib/' + module.replace('Qt', 'Qt5') + suffix
for module in qt_modules]
dll_files.extend(qt_modules)
# https://doc.qt.io/qt-5/windows-deployment.html
# "If dynamic OpenGL is used, you additionally need to include the
# libraries required for ANGLE and software rendering. For ANGLE, both
# libEGL.dll and libGLESv2.dll from Qt's lib directory are required as
# well as the HLSL compiler from DirectX. The HLSL compiler library,
# d3dcompiler_XX.dll, where XX is the version number that ANGLE
# (libGLESv2) was linked against."
dll_files.extend(['$QTDIR/bin/libEGL' + suffix,
'$QTDIR/bin/libGLESv2' + suffix])
d3dcompiler_path = util.find_d3dcompiler_dll(build.env)
if d3dcompiler_path:
dll_files.append(d3dcompiler_path)
# Qt imageformats plugin
imgfmtdll_files = []
qt_imagesformats = depends.Qt.enabled_imageformats(build)
suffix = 'd.dll' if build.build_is_debug else '.dll'
if not build.static_qt:
imgfmtdll_files.extend(['$QTDIR/plugins/imageformats/' + module + suffix for module in qt_imagesformats])
# We don't have Qt's dll pdb files in our release build environements, so only if build is debug
pdbSuffix = 'd.pdb' if (build.bundle_pdbs and build.build_is_debug) else ''
if pdbSuffix:
imgfmtdll_files.extend(['$QTDIR/plugins/imageformats/' + module + pdbSuffix for module in qt_imagesformats])
sqldll_files = []
if int(flags.get('qt_sqlite_plugin', 0)):
# TODO(rryan): Add the SQLite DLL For Qt5.
pass
if build.platform_is_linux or build.platform_is_bsd:
flags['prefix'] = ARGUMENTS.get('prefix', '/usr/local')
if not os.path.exists(flags['prefix']):
print("Error: Prefix path does not exist!")
Exit(1)
else:
#install_root is used in Debian/Ubuntu packaging (check the debian/rules file in the Ubuntu package)
#Basically, the flags['prefix'] is compiled into strings in Mixxx, whereas the install_root is not. When you're
#building a Debian package, pbuilder wants to install Mixxx to a temporary directory, but you still need
#the compiled-in strings using /usr as the prefix. That's why we have install_root and flags['prefix'].
install_root = ARGUMENTS.get('install_root', flags['prefix'])
print("Install root: " + install_root)
unix_share_path = os.path.join(install_root,
env.get('SHAREDIR', default='share'))
unix_bin_path = os.path.join(install_root,
env.get('BINDIR', default='bin'))
unix_lib_path = os.path.join(install_root,
env.get('LIBDIR', default='lib'))
binary = env.Install(unix_bin_path, binary_files)
skins = env.Install(os.path.join(unix_share_path, 'mixxx', 'skins'), skin_files)
fonts = env.Install(os.path.join(unix_share_path, 'mixxx', 'fonts'), font_files)
controllermappings = env.Install(os.path.join(unix_share_path, 'mixxx', 'controllers'), controllermappings_files)
translations = env.Install(os.path.join(unix_share_path, 'mixxx', 'translations'), translation_files)
keyboardmappings = env.Install(os.path.join(unix_share_path, 'mixxx', 'keyboard'), keyboardmappings_files)
dotdesktop = env.Install(os.path.join(unix_share_path, 'applications'), dotdesktop_files)
dotappstream = env.Install(os.path.join(unix_share_path, 'appdata'), dotappstream_files)
docs = env.Install(os.path.join(unix_share_path, 'doc', 'mixxx'), docs_files)
icon = env.Install(os.path.join(unix_share_path, 'pixmaps'), icon_files)
# NOTE(rryan): Hack to detect when we're Debian packaging.
building_debian_package = 'debian/tmp/usr' in install_root
udev_root = '/etc/udev/rules.d'
hidudev = env.Install(udev_root, hidudev_files)
#Makes each of those Install builders get fired off when you run "scons install" :)
env.Alias('install', binary)
env.Alias('install', skins)
env.Alias('install', fonts)
env.Alias('install', controllermappings)
env.Alias('install', translations)
env.Alias('install', keyboardmappings)
env.Alias('install', docs)
env.Alias('install', dotdesktop)
env.Alias('install', dotappstream)
env.Alias('install', icon)
if not building_debian_package and os.access(udev_root, os.W_OK):
env.Alias('install', hidudev)
#Build the Mixxx.app bundle
if build.platform_is_osx and 'bundle' in COMMAND_LINE_TARGETS:
#Mixxx build variables
VOLNAME="Mixxx" #tmp tmp tmp, it's unclean to pass this into build_dmg this way. perhaps pass it in the env?
ARCH = 'ppc' if build.machine in ['powerpc', 'powerpc64'] else 'macintel'
ARCH += ("64" if build.machine_is_64bit else "32")
DMG_ICON="#res/osx/VolumeIcon.icns"
# In Qt 5, the SQLite driver was moved out of QtSql and into a plugin.
sql_dylibs = ["libqsqlite.dylib"]
qt_plugins = (
[("iconengines", e) for e in ["libqsvgicon.dylib"]] +
# Left out libqmng and libqtiff to save space.
[("imageformats", e) for e in
["libqgif.dylib", "libqjpeg.dylib", "libqsvg.dylib"]] +
# Cocoa support moved to a plugin in Qt 5.
[("platforms", "libqcocoa.dylib")] +
[("sqldrivers", e) for e in sql_dylibs] +
[("styles", "libqmacstyle.dylib")]
)
resource_map = {}
for tfile in translation_files:
resource_map[str(tfile)] = 'translations'
qtdir = build.env['QTDIR']
qt_frameworks = depends.Qt.find_framework_libdir(qtdir)
if not qt_frameworks:
raise Exception('Could not find frameworks in Qt directory: %s' % qtdir)
#qt_menu.nib for Cocoa Qt 4.7+
menu_nib = os.path.join(qt_frameworks, 'QtGui.framework',
'Resources', 'qt_menu.nib')
otool_local_paths = [os.path.expanduser("~/Library/Frameworks"),
qt_frameworks,
"/Library/Frameworks",
"/Network/Library/Frameworks",
"/usr/local/lib",
"/opt/local/lib",
"/sw/local/lib"]
otool_system_paths = ["/System/Library/Frameworks",
"/Network/Library/Frameworks",
"/usr/lib"]
mixxx_osxlib_path = SCons.ARGUMENTS.get('osxlib', None)
if mixxx_osxlib_path:
otool_local_paths = [mixxx_osxlib_path,] + otool_local_paths
qtplugindir = SCons.ARGUMENTS.get('qtplugindir', None)
if not qtplugindir:
#qtplugindir = '/Developer/Applications/Qt/'
qtplugindir = qtdir
sources = [mixxx_bin,
'#res/osx/application.icns',
Dir('#res/skins/'),
Dir('#res/controllers/'),
Dir('#res/fonts/'),
translation_files,
Dir('#res/keyboard/'),
Dir('#res/doc/'),
Dir(menu_nib),
File("#README"),
File("#LICENSE")]
bundle = env.App(
"Mixxx_bundle",
sources,
PLUGINS=plugins, ##XXX test what happens if we don't pass any plugins
#Qt plugins ((Qt *NEEDS* its plugins in specific locations or it refuses to find them, however this is clearly awkward to write out like this.. maybe))
QT_HACK = [(p_tgt_dir, os.path.join(qtplugindir, "plugins", p_tgt_dir, p)) for p_tgt_dir, p in qt_plugins], #sigh :(
APP_RESOURCES_MAP=resource_map,
IDENTIFIER="org.mixxx.mixxx",
DISPLAY_NAME="Mixxx",
VERSION=mixxx_version,
SHORT_VERSION=mixxx_version,
COPYRIGHT="Copyright © 2001-%s Mixxx Development Team" % datetime.datetime.now().year,
MINIMUM_OSX_VERSION=util.get_osx_min_version(),
CATEGORY="public.app-category.music",
OTOOL_LOCAL_PATHS=otool_local_paths,
OTOOL_SYSTEM_PATHS=otool_system_paths,
FOR_APP_STORE=int(build.flags['macappstore']) > 0,
)
env.Alias('bundle', bundle)
codesign_installer_identity = SCons.ARGUMENTS.get('osx_codesign_installer_identity', None)
codesign_application_identity = SCons.ARGUMENTS.get('osx_codesign_application_identity', None)
codesign_keychain = SCons.ARGUMENTS.get('osx_codesign_keychain', None)
codesign_keychain_password = SCons.ARGUMENTS.get('osx_codesign_keychain_password', None)
codesign_entitlements = SCons.ARGUMENTS.get('osx_codesign_entitlements', None)
# CodeSign needs to take sources for it source so that there is an input
# that changse. Otherwise SCons will think the CodeSign target is up to
# date and not run it.
codesign = env.CodeSign(
'Mixxx_codesign',
sources,
CODESIGN_INSTALLER_IDENTITY=codesign_installer_identity,
CODESIGN_APPLICATION_IDENTITY=codesign_application_identity,
CODESIGN_KEYCHAIN=codesign_keychain,
CODESIGN_KEYCHAIN_PASSWORD=codesign_keychain_password,
CODESIGN_ENTITLEMENTS=codesign_entitlements)
env.AlwaysBuild(codesign)
env.Alias('sign', codesign)
package_name = 'mixxx'
package_version = construct_version(build, mixxx_version, branch_name,
vcs_revision)
dmg_name = '%s-%s-%s' % (package_name, package_version, ARCH)
dmg = env.Dmg(dmg_name, [bundle, ] + docs_files, VOLNAME=VOLNAME, ICON = DMG_ICON)
env.Alias('package', dmg)
if build.platform_is_windows:
base_dist_dir = '#' + dist_dir
skins = env.Install(os.path.join(base_dist_dir, "skins"), skin_files)
controllermappings = env.Install(os.path.join(base_dist_dir, "controllers"), controllermappings_files)
fonts = env.Install(os.path.join(base_dist_dir, "fonts"), font_files)
translations = env.Install(os.path.join(base_dist_dir, "translations"), translation_files)
keyboardmappings = env.Install(os.path.join(base_dist_dir, "keyboard"), keyboardmappings_files)
docs = env.Install(os.path.join(base_dist_dir, "doc/"), docs_files)
#icon = env.Install(base_dist_dir+"", icon_files)
dlls = env.Install(base_dist_dir+"/", dll_files)
binary = env.Install(base_dist_dir+"/", binary_files)
#Always trigger these install builders when compiling on Windows
env.Alias('mixxx', skins)
env.Alias('mixxx', controllermappings)
env.Alias('mixxx', fonts)
env.Alias('mixxx', translations)
env.Alias('mixxx', keyboardmappings)
env.Alias('mixxx', docs)
env.Alias('mixxx', dlls)
#env.Alias('mixxx', icon)
env.Alias('mixxx', binary)
binaries_to_codesign = [binary, dlls]
# imageformats DLL
if imgfmtdll_files:
imageformats_dll = env.Install(os.path.join(base_dist_dir, "imageformats"), imgfmtdll_files)
binaries_to_codesign.append(imageformats_dll)
env.Alias('mixxx', imageformats_dll)
# QSQLite DLL
if sqldll_files:
sql_dlls = env.Install(os.path.join(base_dist_dir, "sqldrivers"), sqldll_files)
binaries_to_codesign.append(sql_dlls)
env.Alias('mixxx', sql_dlls)
if 'sign' in COMMAND_LINE_TARGETS:
codesign_subject_name = SCons.ARGUMENTS.get('windows_codesign_subject_name', '')
if not codesign_subject_name:
raise Exception('Code-signing was requested but windows_codesign_subject_name was not provided.')
codesign = env.SignTool(
'Mixxx_signtool',
binaries_to_codesign,
SUBJECT_NAME=codesign_subject_name)
env.Alias('sign', codesign)
def BuildRelease(target, source, env):
print("==== Mixxx Post-Build Checks ====")
print("You have built version %s" % mixxx_version)
if build.build_is_debug:
print("YOU ARE ABOUT TO PACKAGE A DEBUG BUILD!!")
print("Binary has size ", end='')
if build.platform_is_windows:
os.system('for %I in ('+dist_dir+'\mixxx.exe) do @echo %~zI')
else:
os.system('ls -lh '+dist_dir+'/mixxx.exe | cut -d \' \' -f 5')
print("Installer file ", end='')
package_name = 'mixxx'
package_version = construct_version(build, mixxx_version, branch_name,
vcs_revision)
arch = "x64" if build.machine_is_64bit else "x86"
msi_name = '%s-%s-%s.msi' % (package_name, package_version, arch)
print(msi_name)
print("Top line of README, check version:")
if build.platform_is_windows:
os.system('for /l %l in (1,1,1) do @for /f "tokens=1,2* delims=:" %a in (\'findstr /n /r "^" README ^| findstr /r "^%l:"\') do @echo %b')
else:
os.system('head -n 1 README')
print("Top 2 lines of LICENSE, check version and copyright dates:")
if build.platform_is_windows:
os.system('for /l %l in (1,1,2) do @for /f "tokens=1,2* delims=:" %a in (\'findstr /n /r "^" LICENSE ^| findstr /r "^%l:"\') do @echo %b')
else:
os.system('head -n 2 LICENSE')
#if (raw_input("Go ahead and build installer (yes/[no])? ") == "yes"):
if True:
# TODO(XXX): Installing a runtime isn't specific to MSVS?
if build.toolchain_is_msvs:
redist_file = 'vc_redist.%s.exe' % arch
print("Searching for the Visual C++ DLL installer package" + redist_file)
# Check for the runtime installer in the winlib root.
redist_path = '%s' % os.path.join(build.winlib_path, redist_file)
print(" ", redist_path,)
if not os.path.isfile(redist_path):
raise Exception('Could not find the MSVC++ runtime installer.')
print("Now building installation package...")
print("Looking for WIX Toolset...")
wix_path = None
if not build.crosscompile and build.platform_is_windows:
wix_directory = os.getenv('WIX')
wix_path = '%s' % os.path.join(wix_directory, "bin")
elif build.crosscompile and build.platform_is_windows:
# TODO(XXX) How to handle that ? what does this exactly means ?
raise NotImplementedError
if not wix_directory:
raise Exception ('Cannot find WIX Toolkit. Do you have it installed?')
else:
print(" Found Wix Toolset in " + wix_path)
WinSDK_path = 'build\\wix'
if not os.path.isfile(os.path.join(WinSDK_path, 'wisubstg.vbs')):
raise Exception ('can not find ' + WinSDK_path + '\wisubstg.vbs')
if not os.path.isfile(os.path.join(WinSDK_path, 'WiLangId.vbs')):
raise Exception ('can not find ' + WinSDK_path + '\WiLangId.vbs')
# Generating random ProductID (should change on every run)
# and put it in mixxx.wxs using the template
ProductID = str(uuid.uuid1()).upper()
with open("build/wix/ProductID.wxi.in", "rt") as fin:
with open("build/wix/ProductID.wxi", "wt") as fout:
for line in fin:
fout.write(line.replace('@PRODUCT_ID@', ProductID))
fin.close()
fout.close()
# The default language
defaultLanguage="en-us"
# The langIds contained in the installer. starting with LangId of the default language
langIds="1033"
winArch = "x64" if build.machine_is_64bit else "x86"
# Auto-create wxs file for each subdir and compile them
print("*** Building intermediate files")
for subdir in next(os.walk(dist_dir))[1]:
print(" " + dist_dir + "\\" + subdir)
# Exclude doc and imageformats helper DLLs, they are bundled elsewhere
if subdir in ['doc', 'imageformats']:
continue
command = '"%(wix)s\\heat.exe" dir %(distdir)s\%(sub)s -nologo -sfrag -suid -ag -srd -cg %(sub)sComp -dr %(sub)sDir -out build\wix\subdirs\%(sub)s.wxs -sw5150 -var var.%(sub)sVar' % \
{'wix': wix_path,
'distdir': dist_dir,
'sub': subdir}
print("Using Command: " + command)
subprocess.check_call(command)
command = '"%(wix)s\\candle.exe" -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -d%(sub)sVar=%(distdir)s\%(sub)s -arch %(arch)s -out build\wix\subdirs\%(sub)s.wixobj build\wix\subdirs\%(sub)s.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'distdir': dist_dir,
'sub': subdir}
print("Using Command: " + command)
subprocess.check_call(command)
# Handle QT's imageformats helper DLLs if dynamic QT
imageformats = "no"
if os.path.exists(os.path.join(dist_dir,"imageformats")) and not build.static_qt:
imageformats = "yes"
command = '"%(wix)s\\heat.exe" dir %(distdir)s\%(sub)s -nologo -sfrag -suid -ag -srd -cg %(sub)sComp -dr %(sub)sDir -out build\wix\subdirs\%(sub)s.wxs -sw5150 -var var.%(sub)sVar' % \
{'wix': wix_path,
'distdir': dist_dir,
'sub': "imageformats"}
print("Using Command: " + command)
subprocess.check_call(command)
command = '"%(wix)s\\candle.exe" -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -d%(sub)sVar=%(distdir)s\%(sub)s -arch %(arch)s -out build\wix\subdirs\%(sub)s.wixobj build\wix\subdirs\%(sub)s.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'distdir': dist_dir,
'sub': "imageformats"}
print("Using Command: " + command)
subprocess.check_call(command)
# Harvest main DLL from install dir
command = '"%(wix)s\\heat.exe" dir %(distdir)s -nologo -sfrag -suid -ag -srd -cg mainDLLCompGroup -dr INSTALLDIR -out build\wix\subdirs\mainDLL.wxs -sw5150 -var var.SourceDir -t build\wix\only-dll.xslt' % \
{'wix': wix_path,
'distdir': dist_dir}
print("Using Command: " + command)
subprocess.check_call(command)
command = '"%(wix)s\\candle.exe" -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -dSourceDir=%(distdir)s -arch %(arch)s -out build\wix\subdirs\mainDLL.wixobj build\wix\subdirs\mainDLL.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'distdir': dist_dir}
print("Using Command: " + command)
subprocess.check_call(command)
# Harvest main PDB from install dir if they exist
isPdb = "no"
if build.bundle_pdbs and glob.glob(os.path.join(dist_dir, "*.pdb")):
isPdb = "yes"
command = '"%(wix)s\\heat.exe" dir %(distdir)s -nologo -sfrag -suid -ag -srd -cg mainPDBCompGroup -dr INSTALLDIR -out build\wix\subdirs\mainPDB.wxs -sw5150 -var var.SourceDir -t build\wix\only-pdb.xslt' % \
{'wix': wix_path,
'distdir': dist_dir}
print("Using Command: " + command)
subprocess.check_call(command)
command = '"%(wix)s\\candle.exe" -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -dSourceDir=%(distdir)s -arch %(arch)s -out build\wix\subdirs\mainPDB.wixobj build\wix\subdirs\mainPDB.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'distdir': dist_dir}
print("Using Command: " + command)
subprocess.check_call(command)
# Compile main wix files
command = '"%(wix)s\\candle.exe" -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -dImageformats=%(isimageformats)s -dPDB=%(isPDB)s -arch %(arch)s -out build\wix\mixxx.wixobj build\wix\mixxx.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'isimageformats': imageformats,
'isPDB': isPdb,
'arch': winArch}
print("Using Command: " + command)
subprocess.check_call(command)
# Build package for default language
print("*** Building package for default language " + defaultLanguage)
command = '"%(wix)s\\light.exe" -cc .\ -nologo -sw1076 -spdb -ext WixUIExtension -cultures:%(deflang)s -loc build\wix\Localization\mixxx_%(deflang)s.wxl -out %(package_name)s build\wix\*.wixobj build\wix\subdirs\*.wixobj' % \
{'wix': wix_path,
'deflang': defaultLanguage,
'package_name': 'part.' + msi_name}
print("Using Command: " + command)
subprocess.check_call(command)
bundlelocfile = open("build/wix/bundle/bundleloc.wxs", "w")
bundlelocfile.write("<?xml version='1.0' encoding='windows-1252'?>\n")
bundlelocfile.write("<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>\n")
bundlelocfile.write(" <Fragment Id='FragmentBundleLoc'>\n")
bundlelocfile.write(" <PayloadGroup Id='BundleLoc'>\n")
for file in glob.glob('build\wix\Localization\mixxx_*.wxl'):
doc = minidom.parse(file)
wixloc = doc.getElementsByTagName("WixLocalization")[0]
culture = wixloc.getAttribute("Culture")
strings = doc.getElementsByTagName("String")
LCID = None
for string in strings:
if string.getAttribute('Id') == "Language":
LCID = string.firstChild.data
break
if not LCID:
print("LCID not found, skipping file " + file)
continue
bundlelocfile.write(" <Payload Id=\"thm-%(culture)s\" Compressed=\"yes\" Name=\"%(LCID)s\\thm.wxl\" SourceFile=\"..\\Localization\\mixxx_%(culture)s.wxl\" />\n" %\
{'culture': culture,
'LCID': LCID}
)
# Do not build localized MSI if it's default language
if culture == defaultLanguage:
continue
print("*** Building package transform for locale %(culture)s LangID %(LCID)s" % \
{'culture': culture,
'LCID': LCID})
command = '"%(wix)s\\light.exe" -cc .\ -reusecab -nologo -sw1076 -spdb -ext WixUIExtension -cultures:%(lang)s,%(deflang)s -loc %(wxl_file)s -out %(lang)s.msi build\wix\*.wixobj build\wix\subdirs\*.wixobj' % \
{'wix': wix_path,
'lang': culture,
'deflang': defaultLanguage,
'wxl_file': file}
print("Using Command: " + command)
subprocess.check_call(command)
command = '"%(wix)s\\torch.exe" -nologo -p -t language %(package_name)s %(lang)s.msi -o %(lang)s.mst' % \
{'wix': wix_path,
'lang': culture,
'package_name': 'part.' + msi_name}
print("Using Command: " + command)
subprocess.check_call(command)
command = 'cscript "%(winsdk)s\wisubstg.vbs" %(package_name)s %(lang)s.mst %(langid)s' % \
{'winsdk': WinSDK_path,
'lang': culture,
'package_name': 'part.' + msi_name,
'langid': LCID}
print("Using Command: " + command)
subprocess.check_call(command)
langIds = langIds + "," + LCID
os.remove(culture + ".msi")
os.remove(culture + ".mst")
print("*** Add all supported languages to MSI Package attribute")
command = 'cscript "%(winsdk)s\WiLangId.vbs" %(package_name)s Package %(langid)s' % \
{'winsdk': WinSDK_path,
'package_name': 'part.' + msi_name,
'langid': langIds}
print("Using Command: " + command)
subprocess.check_call(command)
bundlelocfile.write(" </PayloadGroup>\n")
bundlelocfile.write(" </Fragment>\n")
bundlelocfile.write("</Wix>\n")
bundlelocfile.close()
# Everything is OK, now rename the msi to final name
if os.path.isfile(msi_name):
os.remove(msi_name)
os.rename('part.' + msi_name, msi_name)
print("*** Compiling Bundle")
# Compile bundle wix file
command = '"%(wix)s\\candle.exe" -ext WixUtilExtension -ext WixBalExtension -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -dMSIPackage=%(package_name)s -arch %(arch)s -out build\\wix\\bundle\\bundle.wixobj build\\wix\\bundle\\bundle.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'package_name': msi_name}
print("Using Command: " + command)
subprocess.check_call(command)
# bundle localisation references
command = '"%(wix)s\\candle.exe" -ext WixUtilExtension -ext WixBalExtension -nologo -dWINLIBPATH=%(winlibpath)s -dPlatform=%(arch)s -dMSIPackage=%(package_name)s -arch %(arch)s -out build\\wix\\bundle\\bundleloc.wixobj build\\wix\\bundle\\bundleloc.wxs' % \
{'wix': wix_path,
'winlibpath': build.winlib_path,
'arch': winArch,
'package_name': msi_name}
print("Using Command: " + command)
subprocess.check_call(command)
exe_name = os.path.splitext(msi_name)[0] + '.exe'
command = '"%(wix)s\\light.exe" -cc .\ -nologo -sw1076 -spdb -ext WixUtilExtension -ext WixBalExtension -dMSIPackage=%(msi_name)s -cultures:%(deflang)s -loc build\wix\Localization\mixxx_%(deflang)s.wxl -out %(package_name)s build\\wix\\bundle\\*.wixobj' % \
{'wix': wix_path,
'deflang': defaultLanguage,
'msi_name': msi_name,
'package_name': exe_name}
print("Using Command: " + command)
subprocess.check_call(command)
if 'sign' in COMMAND_LINE_TARGETS:
from build.windows import signtool
codesign_subject_name = SCons.ARGUMENTS.get('windows_codesign_subject_name', '')
if not codesign_subject_name:
raise Exception('Code-signing was requested but windows_codesign_subject_name was not provided.')
print("*** Signing Bundle")
# In addition to simply signing the installer executable, we have to
# extract and sign the "burn engine". See
# http://wixtoolset.org/documentation/manual/v3/overview/insignia.html for details.
command = ("%(wix)s\\insignia.exe -ib %(package_name)s -o setup.exe" % {
"wix": wix_path,
"package_name": exe_name,
})
print("Using Command: " + command)
subprocess.check_call(command)
# Imperatively sign the file since the whole WiX process is imperative.
signtool.signtool_path(codesign_subject_name, 'setup.exe')
command = ("%(wix)s\\insignia.exe -ab setup.exe %(package_name)s -o %(package_name)s" % {
"wix": wix_path,
"package_name": exe_name,
})
print("Using Command: " + command)
subprocess.check_call(command)
# Now sign the final package imperatively.
signtool.signtool_path(codesign_subject_name, exe_name)
# Some cleaning before leaving
for file in glob.glob('*.cab'):
os.remove(file)
for file in glob.glob('build\wix\*.wixobj'):
os.remove(file)
for file in glob.glob('build\wix\subdirs\*.wixobj'):
os.remove(file)
for file in glob.glob('build\wix\subdirs\*.wxs'):
os.remove(file)
os.remove(msi_name)
else:
print("Aborted building installer")
# Do release things
versionbld = Builder(action = BuildRelease, suffix = '.foo', src_suffix = '.bar')
env.Append(BUILDERS = {'BuildRelease' : versionbld})
if 'makerelease' in COMMAND_LINE_TARGETS:
makerelease = env.BuildRelease('', binary_files)
env.Alias('makerelease', makerelease)
def ubuntu_append_changelog(debian_dir,
package_name, package_version,
description,
distro='lucid',
urgency='low',
author="Mixxx Buildbot <[email protected]>"):
now_formatted = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
new_entry = [
"%s (%s) %s; urgency=%s" % (package_name, package_version, distro, urgency),
"",
description,
"",
" -- %s %s" % (author, now_formatted),
"",
]
lines = []
with open(os.path.join(debian_dir, 'changelog'), 'r') as changelog:
lines = list(changelog)
with open(os.path.join(debian_dir, 'changelog'), 'w') as changelog:
changelog.writelines(["%s\n" % x for x in new_entry])
changelog.writelines(lines)
def ubuntu_cleanup():
os.system('rm -rf ubuntu')
os.mkdir('ubuntu')
# Build the Ubuntu package
def BuildUbuntuPackage(target, source, env):
global mixxx_version
print("==== Mixxx Post-Build Checks ====")
print("You have built version " + mixxx_version)
print("\n\n")
print("Top line of README, check version:")
os.system('head -n 1 README')
print()
print("Top 2 lines of LICENSE, check version and copyright dates:")
os.system('head -n 2 LICENSE')
print()
print("Top line of debian/ubuntu changelog, check version:")
os.system('head -n 1 build/debian/changelog')
print()
print("Now building DEB package...")
print()
arch = 'amd64' if build.machine_is_64bit else 'i386'
package_target = ARGUMENTS.get('package', None)
ubuntu_distros = ARGUMENTS.get('ubuntu_dist', None)
if ubuntu_distros is None:
print("You did not specify an Ubuntu distribution to target. Specify one with the ubuntu_dist flag.")
# TODO(XXX) default to their current distro? the .pbuilderrc does this
return
ubuntu_version = ARGUMENTS.get('ubuntu_version', '0ubuntu1')
ubuntu_ppa = ARGUMENTS.get('ubuntu_ppa', None)
ubuntu_distros = ubuntu_distros.split(',')
# Big hack for beta PPA upload. We need LP to believe that our original
# package version is always changing otherwise it will reject our orig
# source tarball.
if ubuntu_ppa and 'mixxxbetas' in ubuntu_ppa:
mixxx_version = '%s-%s%s' % (mixxx_version, vcs_name, vcs_revision)
# Destroy ubuntu/ and create it
ubuntu_cleanup()
package_name = 'mixxx'
# directory and original tarball need to have the upstream-release
# version, NOT the package version. For example:
# upstream version: 1.10.0-beta1
# package version: 1.10.0-beta1-0ubuntu1~bzr2206
# directory name: mixxx-1.10.0-beta1
# original tarball: mixxx_1.10.0-beta1.orig.tar.gz
mixxx_dir = '%s-%s' % (package_name, mixxx_version)
# The underscore is super important here to make the deb package work
mixxx_tarball = "%s_%s.orig.tar.gz" % (package_name, mixxx_version)
build_dir = os.path.join('ubuntu', mixxx_dir)
if os.path.exists(build_dir):
print("* Cleaning up %s (cwd: %s)" % (build_dir, os.getcwd()))
print
os.system('rm -rf %s' % build_dir) # be careful.
# TODO: make a get flags arg to accept a revision which can override this and checkout of a specific SVN rev for the package
# Export the source folder
print("* Exporting source folder from current workspace (%s rev: %s)" % (vcs_name,
vcs_revision))
print()
util.export_source('.', build_dir)
# Copy a patch to be included in the exported build sources (this can also be something like src/SConscript, /build/debian/rules)
if os.path.exists('post-export-patch'):
print("* Applying post export patch")
print()
os.system('cp --dereference -r post-export-patch/* %s' % build_dir)
# Write a build.h to the exported directory. Later code looks for a
# build.h in the mixxx/ directory and moves it to build.build_dir/
# instead of generating.
util.write_build_header(os.path.join(build_dir, 'build.h'))
os.chdir('ubuntu')