#!/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')
Import('soundsource_plugins')
Import('mixxxminimal_plugins')

env = build.env
flags = build.flags

#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_bin = env.Program('mixxx',
                            [sources, env.RES('#src/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', sources)
else:
        mixxx_bin = env.Program('mixxx', sources)

# Make sure soundsource and mixxxminimal plugins are built before
# mixxx_bin. This fixes a race where when building with multiple threads the
# packaging step starts before the plugins are built.
Depends(mixxx_bin, mixxxminimal_plugins)
Depends(mixxx_bin, soundsource_plugins)

# 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:
    Command("../mixxx", mixxx_bin, Copy("$TARGET", "$SOURCE"))

test_bin = None
def build_tests():
        global test_bin
        test_files = Glob('test/*.cpp', strings=True)
        test_env = env.Clone()
        test_env.Append(CPPPATH="#lib/gtest-1.7.0/include")
        test_env.Append(CPPPATH="#lib/gmock-1.7.0/include")
        test_env.Append(CPPPATH="#lib/benchmark/include")
        test_files = [test_env.StaticObject(filename)
                      if filename !='main.cpp' else filename
                      for filename in test_files]
        mixxx_sources = [filename for filename in sources if filename != 'main.cpp']
        test_sources = (test_files + mixxx_sources)

        env.Append(LIBPATH="#lib/gtest-1.7.0/lib")
        env.Append(LIBS = 'gtest')

        env.Append(LIBPATH="#lib/gmock-1.7.0/lib")
        env.Append(LIBS = 'gmock')

        env.Append(LIBPATH="#lib/benchmark/lib")
        env.Append(LIBS = 'benchmark')

        if build.platform_is_windows:
                # For SHGetValueA in Google's benchmark library.
                env.Append(LIBS = 'Shlwapi')

                # TODO(rryan): Build Mixxx core as a shared object and link it
                # into mixxx and mixxx-test. We could build both in different
                # environments right now but then automoc and protoc get run in
                # both environments which makes SCons unhappy.
                # Currently both executables are built with /subsystem:windows
                # and the console is attached manually
                test_bin = env.Program(
                        'mixxx-test', [test_sources, env.RES('#src/mixxx.rc')],
                        LINKCOM = [env['LINKCOM'], 'mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;1'])
        else:
                test_bin = env.Program(target='mixxx-test', source=test_sources)

        env.Alias('mixxx-test', test_bin)

        if not build.platform_is_windows:
                Command("../", test_bin, Copy("$TARGET", "$SOURCE"))

def run_tests():
        ret = Execute("./mixxx-test")
        if ret != 0:
                print("WARNING: Not all tests pass. See mixxx-test output.")
                Exit(ret)

if int(build.flags['test']):
        print("Building tests.")
        build_tests()

if 'test' in BUILD_TARGETS:
        print("Running tests.")
        run_tests()

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
#=========================

# flags['prefix'] = ARGUMENTS.get('prefix', '/usr/local')
# if not os.path.exists(flags['prefix']):
#         print("Error: Prefix path does not exist!")
#         Exit(1)
# else:
#        unix_share_path = flags['prefix'] + "/share"
#        unix_bin_path   = flags['prefix'] + "/bin"

#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))

#Soundsource plugins
soundsource_plugin_files = soundsource_plugins

#VAMP beat tracking and key detection plugin
libmixxxminimal_vamp_plugin = mixxxminimal_plugins

#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

# TODO: Use reference to SharedLibrary for libsndfile and others, glob only gets
# all files on 2+ builds after a clean.
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)
qt5 = depends.Qt.qt5_enabled(build)

if build.platform_is_windows:
    suffix = 'd.dll' if build.build_is_debug else '.dll'
    if not build.static_qt:
        if qt5:
            qt_modules = ['$QTDIR/lib/' + module.replace('Qt', 'Qt5') + suffix
                      for module in qt_modules]
            dll_files.extend(qt_modules)
        else:
            qt_modules = ['$QTDIR/lib/' + module + suffix for module in qt_modules]
            dll_files.extend(qt_modules)
    if qt5:
        # 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)

if qt5:
        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])
else:
        suffix = 'd4.dll' if build.build_is_debug else '4.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 = 'd4.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)):
        if qt5:
                # TODO(rryan): Add the SQLite DLL For Qt5.
                pass
        else:
                suffix = 'd4.dll' if build.build_is_debug else '4.dll'
                # Qt SQLite plugin
                sqldll_files = ['$QTDIR/plugins/sqldrivers/qsqlite' + suffix]

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)
                if qt5:
                    vamp_plugin =  env.Install(
                            os.path.join(unix_lib_path, 'mixxx', 'plugins', 'vampqt5'),
                            libmixxxminimal_vamp_plugin)
                    soundsource_plugins = env.Install(
                            os.path.join(unix_lib_path, 'mixxx', 'plugins', 'soundsourceqt5'),
                            soundsource_plugin_files)
                else:
                    vamp_plugin =  env.Install(
                            os.path.join(unix_lib_path, 'mixxx', 'plugins', 'vamp'),
                            libmixxxminimal_vamp_plugin) 
                    soundsource_plugins = env.Install(
                            os.path.join(unix_lib_path, 'mixxx', 'plugins', 'soundsource'),
                            soundsource_plugin_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', soundsource_plugins)
                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)
                env.Alias('install', vamp_plugin)

                if not building_debian_package and os.access(udev_root, os.W_OK):
                        env.Alias('install', hidudev)

                #Delete the old Mixxx installation (because SCONS won't overwrite it)
                #if 'install' in COMMAND_LINE_TARGETS:
                        #os.system('scons -c install')
                        #Delete(unix_share_path + "/mixxx/skins")
                        #print("Copying skins...")
                        #env.Command(unix_share_path + "/mixxx/skins", skin_files, Copy("$TARGET", "$SOURCE"), source_scanner = DirScanner)
                        #Copy(unix_share_path + "/.ixxx/skins", skin_files)
                        #Delete(unix_bin_path + "mixxx")

                        #Delete(unix_share_path + "/mixxx/controllers")
                        #Delete(unix_share_path + "/mixxx/keyboard")

#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"

        # this is a BIG HACK to support Qt's plugins (since Qt *requires* that
        # it's plugins be in specific subdirectories, which OS X doesn't really
        # play nice with)

        # NOTE(rryan): Only include the SQLite plugin if we are building Qt in
        # sqlite_plugin mode or Qt 5 is enabled. In Qt 5, the SQLite driver was
        # moved out of QtSql and into a plugin.
        sql_dylibs = []
        if int(flags.get('qt_sqlite_plugin', 0)) or qt5:
                sql_dylibs = ["libqsqlite.dylib"]

        qt_plugins = (
                [("iconengines", e) for e in ["libqsvgicon.dylib"]] +
                [("imageformats", e) for e in ["libqgif.dylib", "libqjpeg.dylib", "libqsvg.dylib"]] +
                [("sqldrivers", e) for e in sql_dylibs])

        # In Qt 5, accessibility features are more tightly integrated.
        if not qt5:
                qt_plugins.append(("accessible", "libqtaccessiblewidgets.dylib"))

        if qt5:
                # Cocoa support moved to a plugin in Qt 5.
                qt_plugins.append(("platforms", "libqcocoa.dylib"))
                qt_plugins.append(("styles", "libqmacstyle.dylib"))

        #Left out libqmng and libqtiff to save space.

        # Concatenate the SoundSource plugins to our list of plugins (converting
        # from SCons File nodes to strings)
        for x in soundsource_plugins:
                plugins.append(x.get_abspath())

        for x in mixxxminimal_plugins:
                plugins.append(x.get_abspath())

        resource_map = {}
        for tfile in translation_files:
                resource_map[str(tfile)] = 'translations'

        qtdir = build.env['QTDIR']
        qt_frameworks = depends.Qt.find_framework_libdir(qtdir, qt5)
        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.Default(mixxx_bin) #todo: make the Default() just the program itself *globally* (not just for OS X); bundle should be a separate target
        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)
        soundsource_plugins = env.Install(os.path.join(base_dist_dir, "plugins", "soundsource/"),
                                          soundsource_plugin_files)
        vamp_plugins = env.Install(os.path.join(base_dist_dir, "plugins", "vamp/"),
                                   libmixxxminimal_vamp_plugin)
        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', soundsource_plugins)
        env.Alias('mixxx', vamp_plugins)
        #env.Alias('mixxx', icon)
        env.Alias('mixxx', binary)

        binaries_to_codesign = [binary, dlls, vamp_plugins, soundsource_plugins]

        # 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 win32_find_program_via_registry(program_name):
    # Windows registry access to find where program is installed
    import _winreg
    hklm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
    program_location_handle = None
    try:
        program_location_handle = _winreg.OpenKey(hklm, "SOFTWARE\\"+program_name, 0, _winreg.KEY_READ)
    except WindowsError:
        program_location_handle = None

    if not program_location_handle:
        try:
            program_location_handle = _winreg.OpenKey(hklm, "SOFTWARE\\Wow6432Node\\"+program_name, 0, _winreg.KEY_READ)
        except WindowsError:
            program_location_handle = None

    program_location = _winreg.QueryValue(program_location_handle, None)
    if not program_location:
        try:
            program_location_tuple = _winreg.QueryValueEx(program_location_handle, "Path")
            program_location = program_location_tuple[0]
        except WindowsError:
            program_location = None
    if not program_location:
        try:
            program_location_tuple = _winreg.QueryValueEx(program_location_handle, "CurrentInstallFolder")
            program_location = program_location_tuple[0]
        except WindowsError:
            program_location = None

    _winreg.CloseKey(hklm)
    return program_location

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 <builds@mixxx.org>"):
        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,
                'qt5=%d' % qt5])

        # 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 <builds@mixxx.org>")

                # 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()

                command = ['MIXXX_SCONS_FLAGS="%s"' % scons_flags,
                           'ARCH=%s' % arch,
                           'DIST=%s' % ubuntu_distro]

                if package_target == 'source':
                        # TODO(rryan) we have to figure out the key-signing situation
                        # here.
                        num_jobs = GetOption('num_jobs')
                        command.extend(['debuild',
                                        # Preserve the MIXXX_SCONS_FLAGS
                                        # environment variable.
                                        '-eMIXXX_SCONS_FLAGS',
                                        # 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.
                                        '-eDEB_BUILD_OPTIONS="parallel=%s"' % num_jobs,
                                        '-S', '-sa',])
                else:
                        command.extend(['pdebuild'])
                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_path = "/var/cache/pbuilder/%s-%s/result/" % (ubuntu_distro, arch)
                        result_filename = "%s_%s_%s.deb" % (package_name, package_version, arch)
                        result_file = os.path.join(result_path, result_filename)

                        # 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("Done! Package and tarballs are in %s" % result_path)
                            print("* Found package at '%s'. Copying to ubuntu/" % result_file)
                            print()
                            shutil.copyfile(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", "defs_version.h" ) #(binary_files)
        env.Alias('makeubuntu', makeubuntu)
