#!/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.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("\n")
bundlelocfile.write("\n")
bundlelocfile.write(" \n")
bundlelocfile.write(" \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(" \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(" \n")
bundlelocfile.write(" \n")
bundlelocfile.write("\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 "):
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')
# Tar the source code
print("* Tarring source directory to '%s' ... (this can take a couple minutes)" % os.path.join(os.getcwd(), mixxx_tarball))
print()
os.system('rm -f "%s"' % mixxx_tarball) #Remove old tarball
os.system('tar --exclude build/debian --exclude=debian --exclude=debian/* -czf "%s" %s' % (mixxx_tarball, mixxx_dir))
os.chdir(mixxx_dir)
# Copy the debian folder from /build/debian to exported source folder root
print("* Copying Debian build directory from build/debian to debian (cwd: %s)" % os.getcwd())
print()
os.system('cp -r build/debian .')
os.system('cp res/linux/mixxx.usb.rules ./debian/mixxx.mixxx-usb.udev')
scons_flags = ' '.join([
'optimize=portable',
'virtualize=0',
'mad=1',
'localecompare=1',
'qt_sqlite_plugin=0',
'build=' + build.build,
])
# Replace environment variables in the rules file.
# TODO(rryan) something more elegant? I don't know a better way. When
# Ubuntu build servers build us we don't get the chance to pass
# environment variables in.
with open('debian/rules', 'r') as fr:
rules = fr.read()
rules = rules.replace('MIXXX_SCONS_FLAGS = ""',
'MIXXX_SCONS_FLAGS = %s' % scons_flags)
with open('debian/rules', 'w') as fw:
fw.write(rules)
for ubuntu_distro in ubuntu_distros:
# if a control.$distro file exists, use it
if os.path.exists('debian/control.%s' % ubuntu_distro):
os.system('cp debian/control.%s debian/control' % ubuntu_distro)
package_version = ubuntu_construct_version(build, mixxx_version,
branch_name, vcs_revision,
ubuntu_version, ubuntu_distro)
ubuntu_signing_identity = SCons.ARGUMENTS.get('ubuntu_signing_identity', "Mixxx Buildbot ")
# Add a changelog record for this package
if build.build_is_debug:
description = " * Experimental build of branch '%s' at revision %s" % (branch_name, vcs_revision)
ubuntu_append_changelog('debian', package_name, package_version,
description, distro=ubuntu_distro,
author=ubuntu_signing_identity)
else:
description = " * New upstream release."
ubuntu_append_changelog('debian', package_name, package_version,
description,
distro=ubuntu_distro,
author=ubuntu_signing_identity)
# Run pbuilder
print("* Starting pbuilder ... (cwd: %s)" % os.getcwd())
print()
num_jobs = GetOption('num_jobs')
command = ['MIXXX_SCONS_FLAGS="%s"' % scons_flags,
'ARCH=%s' % arch,
'DIST=%s' % ubuntu_distro,
# Pass the scons -jX option in in
# DEB_BUILD_OPTIONS. The Debian package rules file
# reads this option to set the -jX flag on the scons
# commands it runs. We disable Debian package
# optimizations via noopt because we handle our own
# optimization flags.
'DEB_BUILD_OPTIONS="noopt parallel=%s"' % num_jobs]
if package_target == 'source':
command.extend(['debuild',
# Preserve the MIXXX_SCONS_FLAGS and DEB_BUILD_OPTIONS
# environment variable.
'-eMIXXX_SCONS_FLAGS',
'-eDEB_BUILD_OPTIONS',
'-S', '-sa',])
else:
command.extend(['pdebuild', '--', '--buildresult ./'])
result = os.system(' '.join(command))
source_changes_file = os.path.join(
'..', '%s_%s_source.changes' % (package_name, package_version))
if package_target == 'source':
if result == 0 and os.path.exists(source_changes_file):
print("* Done! Signed source package is in ubuntu/")
print()
else:
print("* Build failed.")
print()
raise Exception('Ubuntu source package build/signing failed.')
else:
result_file = "%s_%s_%s.deb" % (package_name, package_version, arch)
# Since we might build for multiple distros we need to
# insert the distro name into the filename.
# HACK(rryan): filenames for Ubuntu packaging in general
# are a big mess but we only distribute files in this
# code path (package_target != 'source') via
# downloads.mixxx.org so we may as well make the
# filenames match the Windows/OSX builds.
version = construct_version(build, mixxx_version,
branch_name, vcs_revision)
dest_filename = '-'.join((package_name, version,
ubuntu_distro, arch))
dest_deb_filename = "%s.deb" % dest_filename
# Also rename the source tarball.
dest_tar_filename = "%s.tar.gz" % dest_filename
# ubuntu/ is one folder up
dest_deb_file = os.path.join('..', dest_deb_filename)
dest_tar_file = os.path.join('..', dest_tar_filename)
source_tar_file = os.path.join('..', mixxx_tarball)
if os.path.exists(source_tar_file):
shutil.move(source_tar_file, dest_tar_file)
if result == 0 and os.path.exists(result_file):
print("* Found package '%s'. Copying to ubuntu/" % result_file)
print()
shutil.move(result_file, dest_deb_file)
else:
print("* Build failed.")
print()
raise Exception('Ubuntu package build failed.')
# print("Signing the .deb changes file...")
# os.system('sudo debsign /var/cache/pbuilder/result/*.changes')
if ubuntu_ppa is not None:
# dput this changes file to the PPA
dput_command = 'dput %s %s' % (ubuntu_ppa, source_changes_file)
print("* Uploading package for", ubuntu_distro, "to launchpad:", dput_command)
result = os.system(dput_command)
if result == 0:
print("* Package upload succeeded.")
else:
print("* Package upload failed.")
print()
raise Exception('Ubuntu package upload failed.')
# Return back to the starting directory, otherwise you'll get a .sconsign.dblite error!
os.chdir('../..')
print("* Returning to starting working directory ... (cwd: " + os.getcwd() + ")")
print()
#Build the Ubuntu package if "makeubuntu" was passed as an argument
versiondebbld = Builder(action = BuildUbuntuPackage) #, suffix = '.foo', src_suffix = '.bar')
env.Append(BUILDERS = {'BuildUbuntuPackage' : versiondebbld})
if 'makeubuntu' in COMMAND_LINE_TARGETS:
makeubuntu = env.BuildUbuntuPackage("blah", "src/defs_version.h" ) #(binary_files)
env.Alias('makeubuntu', makeubuntu)