make-ring.py 16.4 KB
Newer Older
aviau's avatar
aviau committed
1 2 3 4 5 6 7 8 9 10 11 12 13
#!/usr/bin/env python3
#
# This is the Ring build helper, it can do these things:
#  - Build Ring
#  - Install Ring
#  - Run Ring
#

import argparse
import os
import subprocess
import sys
import time
Alexandre Lision's avatar
Alexandre Lision committed
14
import platform
15
import multiprocessing
16
import shutil
aviau's avatar
aviau committed
17

18
IOS_DISTRIBUTION_NAME="iOS"
19 20
OSX_DISTRIBUTION_NAME="OSX"
ANDROID_DISTRIBUTION_NAME="Android"
21

22
APT_BASED_DISTROS = [
aviau's avatar
aviau committed
23 24 25 26
    'Debian',
    'Ubuntu',
]

27
DNF_BASED_DISTROS = [
28 29 30
    'Fedora',
]

Simon Zeni's avatar
Simon Zeni committed
31 32 33 34
PACMAN_BASED_DISTROS = [
    'Arch Linux',
]

35
ZYPPER_BASED_DISTROS = [
36 37 38
    'openSUSE',
]

aviau's avatar
aviau committed
39 40
APT_INSTALL_SCRIPT = [
    'apt-get update',
41
    'apt-get install -y %(packages)s'
aviau's avatar
aviau committed
42 43
]

44 45 46 47
BREW_UNLINK_SCRIPT = [
    'brew unlink %(packages)s'
]

Alexandre Lision's avatar
Alexandre Lision committed
48 49 50
BREW_INSTALL_SCRIPT = [
    'brew update',
    'brew install -y %(packages)s',
51
    'brew link --force --overwrite %(packages)s'
Alexandre Lision's avatar
Alexandre Lision committed
52
]
Simon Zeni's avatar
Simon Zeni committed
53

54 55 56 57 58
RPM_INSTALL_SCRIPT = [
    'sudo dnf update',
    'sudo dnf install -y %(packages)s'
]

Simon Zeni's avatar
Simon Zeni committed
59 60 61 62 63
PACMAN_INSTALL_SCRIPT = [
    'sudo pacman -Sy',
    'sudo pacman -S %(packages)s'
]

64 65 66 67 68
ZYPPER_INSTALL_SCRIPT = [
    'sudo zypper update',
    'sudo zypper install -y %(packages)s'
]

69
ZYPPER_DEPENDENCIES = [
70
# build system
71
    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'patch', 'gcc-c++', 'libtool',
72 73 74 75 76
# daemon
    'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
    'libupnp-devel', 'boost-devel', 'yasm', 'libuuid-devel', 'libsamplerate-devel',
    'libnettle-devel', 'libopus-devel', 'libgnutls-devel', 'msgpack-devel', 'libavcodec-devel',
    'libavdevice-devel', 'pcre-devel', 'libogg-devel', 'libsndfile-devel', 'libvorbis-devel',
77
    'flac-devel', 'libgsm-devel', 'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel',
78
    'libvdpau-devel', 'libopenssl-devel',
79 80 81 82
# lrc
    'libQt5Core-devel', 'libQt5DBus-devel', 'libqt5-linguist-devel',
# gnome client
    'gtk3-devel', 'clutter-gtk-devel', 'qrencode-devel', 'evolution-data-server-devel',
83
    'gettext-tools', 'libnotify-devel', 'libappindicator3-devel', 'webkit2gtk3-devel',
84
    'NetworkManager-devel', 'libcanberra-gtk3-devel'
85 86
]

87 88 89
MINGW64_FEDORA_DEPENDENCIES = [
    'mingw64-binutils', 'mingw64-gcc', 'mingw64-headers', 'mingw64-crt', 'mingw64-gcc-c++',
    'mingw64-pkg-config', 'yasm', 'gettext-devel', 'cmake', 'patch', 'libtool', 'automake',
90
    'autoconf', 'autoconf-archive', 'make', 'xz', 'bzip2', 'which', 'mingw64-qt5-qtbase',
91 92
    'mingw64-qt5-qttools', 'mingw64-qt5-qtsvg', 'mingw64-qt5-qtwinextras', 'mingw64-libidn',
    'mingw64-xz-libs','msgpack-devel'
93 94 95 96 97
]

MINGW32_FEDORA_DEPENDENCIES = [
    'mingw32-binutils', 'mingw32-gcc', 'mingw32-headers', 'mingw32-crt', 'mingw32-gcc-c++',
    'mingw32-pkg-config', 'yasm', 'gettext-devel', 'cmake', 'patch', 'libtool', 'automake',
98
    'autoconf', 'autoconf-archive', 'make', 'xz', 'bzip2', 'which', 'mingw32-qt5-qtbase',
99 100
    'mingw32-qt5-qttools', 'mingw32-qt5-qtsvg', 'mingw32-qt5-qtwinextras', 'mingw32-libidn',
    'mingw32-xz-libs', 'msgpack-devel'
101 102
]

103
DNF_DEPENDENCIES = [
104
    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'speexdsp-devel', 'pulseaudio-libs-devel',
105 106
    'libsamplerate-devel', 'libtool', 'dbus-devel', 'expat-devel', 'pcre-devel',
    'yaml-cpp-devel', 'boost-devel', 'dbus-c++-devel', 'dbus-devel',
107
    'libsndfile-devel', 'libXext-devel', 'libXfixes-devel', 'yasm',
108
    'speex-devel', 'chrpath', 'check', 'astyle', 'uuid-c++-devel', 'gettext-devel',
109 110
    'gcc-c++', 'which', 'alsa-lib-devel', 'systemd-devel', 'libuuid-devel',
    'uuid-devel', 'gnutls-devel', 'nettle-devel', 'opus-devel', 'speexdsp-devel',
111 112
    'yaml-cpp-devel', 'qt5-qtbase-devel', 'swig', 'qrencode-devel', 'jsoncpp-devel',
    'gtk3-devel', 'clutter-devel', 'clutter-gtk-devel', 'evolution-data-server-devel',
113
    'libnotify-devel', 'libappindicator-gtk3-devel', 'patch', 'libva-devel', 'openssl-devel',
114
    'webkitgtk4-devel', 'NetworkManager-libnm-devel', 'libvdpau-devel', 'msgpack-devel', 'libcanberra-devel'
115
]
Alexandre Lision's avatar
Alexandre Lision committed
116

117
APT_DEPENDENCIES = [
118
    'autoconf', 'autoconf-archive', 'autopoint', 'cmake', 'dbus', 'doxygen', 'g++', 'gettext',
aviau's avatar
aviau committed
119 120 121
    'gnome-icon-theme-symbolic', 'libasound2-dev', 'libavcodec-dev',
    'libavcodec-extra', 'libavdevice-dev', 'libavformat-dev', 'libboost-dev',
    'libclutter-gtk-1.0-dev', 'libcppunit-dev', 'libdbus-1-dev',
122
    'libdbus-c++-dev', 'libebook1.2-dev', 'libexpat1-dev', 'libgnutls28-dev',
123
    'libgsm1-dev', 'libgtk-3-dev', 'libjack-dev', 'libnotify-dev',
124
    'libopus-dev', 'libpcre3-dev', 'libpulse-dev', 'libsamplerate0-dev', 'libssl',
125 126
    'libsndfile1-dev', 'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
    'libudev-dev', 'libupnp-dev', 'libyaml-cpp-dev', 'qtbase5-dev', 'sip-tester', 'swig',
127
    'uuid-dev', 'yasm', 'libqrencode-dev', 'libjsoncpp-dev', 'libappindicator3-dev',
128
    'libva-dev', 'libwebkit2gtk-4.0-dev', 'libnm-dev', 'libvdpau-dev', 'libmsgpack-dev', 'libcanberra-gtk3-dev'
aviau's avatar
aviau committed
129 130
]

131
PACMAN_DEPENDENCIES = [
132
    'autoconf', 'autoconf-archive', 'gettext', 'cmake', 'dbus', 'doxygen', 'gcc', 'gnome-icon-theme-symbolic',
Simon Zeni's avatar
Simon Zeni committed
133 134 135 136
    'ffmpeg', 'boost', 'clutter-gtk', 'cppunit', 'libdbus', 'dbus-c++', 'libe-book',
    'expat', 'gsm', 'gtk3', 'jack', 'libnotify', 'opus', 'pcre', 'libpulse', 'libsamplerate',
    'libsndfile', 'speex', 'speexdsp', 'libtool', 'libupnp', 'yaml-cpp', 'qt5-base',
    'swig', 'yasm', 'qrencode', 'evolution-data-server', 'make', 'patch', 'pkg-config',
137
    'automake', 'libva', 'webkit2gtk', 'libnm', 'libvdpau', 'libcanbera', 'openssl'
Simon Zeni's avatar
Simon Zeni committed
138 139
]

Alexandre Lision's avatar
Alexandre Lision committed
140
OSX_DEPENDENCIES = [
141
    'autoconf', 'cmake', 'gettext', 'pkg-config', 'qt5',
Alexandre Lision's avatar
Alexandre Lision committed
142 143 144
    'libtool', 'yasm', 'automake'
]

145 146 147 148 149
OSX_DEPENDENCIES_UNLINK = [
    'autoconf*', 'cmake*', 'gettext*', 'pkg-config*', 'qt*', 'qt@5.*',
    'libtool*', 'yasm*', 'automake*'
]

150
IOS_DEPENDENCIES = [
151
    'autoconf', 'automake', 'cmake', 'yasm', 'libtool',
152 153 154
    'pkg-config', 'gettext', 'swiftlint', 'swiftgen'
]

155 156 157 158 159
IOS_DEPENDENCIES_UNLINK = [
    'autoconf*', 'automake*', 'cmake*', 'yasm*', 'libtool*',
    'pkg-config*', 'gettext*', 'swiftlint*', 'swiftgen*'
]

aviau's avatar
aviau committed
160 161
UNINSTALL_SCRIPT = [
    'make -C daemon uninstall',
162 163 164 165
    'rm -rf ./lrc/build-global/',
    'rm -rf ./lrc/build-local/',
    'rm -rf ./client-gnome/build-global',
    'rm -rf ./client-gnome/build-local',
aviau's avatar
aviau committed
166 167
]

Alexandre Lision's avatar
Alexandre Lision committed
168 169 170 171 172
OSX_UNINSTALL_SCRIPT = [
    'make -C daemon uninstall',
    'rm -rf install/client-macosx',
]

aviau's avatar
aviau committed
173 174 175 176 177 178 179
STOP_SCRIPT = [
    'xargs kill < daemon.pid',
    'xargs kill < gnome-ring.pid',
]


def run_dependencies(args):
180
    if args.distribution in APT_BASED_DISTROS:
aviau's avatar
aviau committed
181
        execute_script(APT_INSTALL_SCRIPT,
182
            {"packages": ' '.join(APT_DEPENDENCIES)}
aviau's avatar
aviau committed
183
        )
184
    elif args.distribution in DNF_BASED_DISTROS:
185 186
        execute_script(
            RPM_INSTALL_SCRIPT,
187
            {"packages": ' '.join(DNF_DEPENDENCIES)}
188
        )
189 190 191 192 193 194 195 196 197 198
    elif args.distribution == "mingw32":
        execute_script(
            RPM_INSTALL_SCRIPT,
            {"packages": ' '.join(MINGW32_FEDORA_DEPENDENCIES)}
        )
    elif args.distribution == "mingw64":
        execute_script(
            RPM_INSTALL_SCRIPT,
            {"packages": ' '.join(MINGW64_FEDORA_DEPENDENCIES)}
        )
199
    elif args.distribution in PACMAN_BASED_DISTROS:
Simon Zeni's avatar
Simon Zeni committed
200 201
        execute_script(
            PACMAN_INSTALL_SCRIPT,
202
            {"packages": ' '.join(PACMAN_DEPENDENCIES)}
Simon Zeni's avatar
Simon Zeni committed
203 204
        )

205
    elif args.distribution in ZYPPER_BASED_DISTROS:
206 207
        execute_script(
            ZYPPER_INSTALL_SCRIPT,
208
            {"packages": ' '.join(ZYPPER_DEPENDENCIES)}
209 210
        )

211
    elif args.distribution == OSX_DISTRIBUTION_NAME:
212 213 214 215
        execute_script(
            BREW_UNLINK_SCRIPT,
            {"packages": ' '.join(OSX_DEPENDENCIES_UNLINK)}
        )
Alexandre Lision's avatar
Alexandre Lision committed
216 217 218 219 220
        execute_script(
            BREW_INSTALL_SCRIPT,
            {"packages": ' '.join(OSX_DEPENDENCIES)}
        )

221
    elif args.distribution == IOS_DISTRIBUTION_NAME:
222 223 224 225
        execute_script(
            BREW_UNLINK_SCRIPT,
            {"packages": ' '.join(IOS_DEPENDENCIES_UNLINK)}
        )
226 227 228 229 230
        execute_script(
            BREW_INSTALL_SCRIPT,
            {"packages": ' '.join(IOS_DEPENDENCIES)}
        )

231
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
232 233 234
        print("The Android version does not need more dependencies.\nPlease continue with the --install instruction.")
        sys.exit(1)

aviau's avatar
aviau committed
235 236 237 238
    else:
        print("Not yet implemented for current distribution (%s)" % args.distribution)
        sys.exit(1)

239
def run_init():
240 241 242 243 244 245 246
    # Extract modules path from '.gitmodules' file
    module_names = []
    with open('.gitmodules') as fd:
        for line in fd.readlines():
            if line.startswith('[submodule "'):
                module_names.append(line[line.find('"')+1:line.rfind('"')])

247 248
    os.system("git submodule update --init")
    os.system("git submodule foreach 'git checkout master && git pull'")
249 250
    for name in module_names:
        copy_file("./scripts/commit-msg", ".git/modules/"+name+"/hooks")
251 252 253 254 255 256 257 258 259 260 261

def copy_file(src, dest):
    print("Copying:" + src + " to " + dest)
    try:
        shutil.copy2(src, dest)
    # eg. src and dest are the same file
    except shutil.Error as e:
        print('Error: %s' % e)
    # eg. source or destination doesn't exist
    except IOError as e:
        print('Error: %s' % e.strerror)
aviau's avatar
aviau committed
262 263

def run_install(args):
264
    install_args = ' -p ' + str(multiprocessing.cpu_count())
aviau's avatar
aviau committed
265 266 267 268
    if args.static:
        install_args += ' -s'
    if args.global_install:
        install_args += ' -g'
269
    if args.distribution == OSX_DISTRIBUTION_NAME:
270
        proc= subprocess.Popen("brew --prefix qt5", shell=True, stdout=subprocess.PIPE)
Alexandre Lision's avatar
Alexandre Lision committed
271 272
        qt5dir = proc.stdout.read()
        os.environ['CMAKE_PREFIX_PATH'] = str(qt5dir.decode('ascii'))
Alexandre Lision's avatar
Alexandre Lision committed
273 274
        install_args += " -c client-macosx"
        execute_script(["CONFIGURE_FLAGS='--without-dbus' ./scripts/install.sh " + install_args])
275 276 277
    elif args.distribution == IOS_DISTRIBUTION_NAME:
        os.chdir("./client-ios")
        execute_script(["./compile-ios.sh"])
278
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
279 280
        os.chdir("./client-android")
        execute_script(["./compile.sh"])
281 282 283 284 285 286 287 288 289 290
    elif args.distribution == 'mingw32':
        os.environ['CMAKE_PREFIX_PATH'] = '/usr/i686-w64-mingw32/sys-root/mingw/lib/cmake'
        os.environ['QTDIR'] = '/usr/i686-w64-mingw32/sys-root/mingw/lib/qt5/'
        os.environ['PATH'] = '/usr/i686-w64-mingw32/bin/qt5/:' + os.environ['PATH']
        execute_script(["./scripts/win_compile.sh"])
    elif args.distribution == 'mingw64':
        os.environ['CMAKE_PREFIX_PATH'] = '/usr/x86_64-w64-mingw32/sys-root/mingw/lib/cmake'
        os.environ['QTDIR'] = '/usr/x86_64-w64-mingw32/sys-root/mingw/lib/qt5/'
        os.environ['PATH'] = '/usr/x86_64-w64-mingw32/bin/qt5/:' + os.environ['PATH']
        execute_script(["./scripts/win_compile.sh --arch=64"])
Alexandre Lision's avatar
Alexandre Lision committed
291
    else:
292
        if args.distribution in ZYPPER_BASED_DISTROS:
293
            os.environ['JSONCPP_LIBS'] = "-ljsoncpp" #fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
294
        install_args += ' -c client-gnome'
Alexandre Lision's avatar
Alexandre Lision committed
295
        execute_script(["./scripts/install.sh " + install_args])
aviau's avatar
aviau committed
296 297 298


def run_uninstall(args):
299
    if args.distribution == OSX_DISTRIBUTION_NAME:
Alexandre Lision's avatar
Alexandre Lision committed
300 301 302
        execute_script(OSX_UNINSTALL_SCRIPT)
    else:
        execute_script(UNINSTALL_SCRIPT)
aviau's avatar
aviau committed
303 304 305


def run_run(args):
306
    if args.distribution == OSX_DISTRIBUTION_NAME:
Alexandre Lision's avatar
Alexandre Lision committed
307 308 309
        subprocess.Popen(["install/client-macosx/Ring.app/Contents/MacOS/Ring"])
        return True

aviau's avatar
aviau committed
310 311 312 313 314 315 316
    run_env = os.environ
    run_env['LD_LIBRARY_PATH'] = run_env.get('LD_LIBRARY_PATH', '') + ":install/lrc/lib"

    try:
        dring_log = open("daemon.log", 'a')
        dring_log.write('=== Starting daemon (%s) ===' % time.strftime("%d/%m/%Y %H:%M:%S"))
        dring_process = subprocess.Popen(
317
            ["./install/daemon/lib/ring/dring", "-c", "-d"],
aviau's avatar
aviau committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
            stdout=dring_log,
            stderr=dring_log
        )

        with open('daemon.pid', 'w') as f:
            f.write(str(dring_process.pid)+'\n')

        client_log = open("gnome-ring.log", 'a')
        client_log.write('=== Starting client (%s) ===' % time.strftime("%d/%m/%Y %H:%M:%S"))
        client_process = subprocess.Popen(
            ["./install/client-gnome/bin/gnome-ring", "-d"],
            stdout=client_log,
            stderr=client_log,
            env=run_env
        )

        with open('gnome-ring.pid', 'w') as f:
            f.write(str(client_process.pid)+'\n')

        if args.debug:
            subprocess.call(
339
                ['gdb','-x', 'gdb.gdb', './install/daemon/lib/ring/dring'],
aviau's avatar
aviau committed
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            )

        if args.background == False:
            dring_process.wait()
            client_process.wait()

    except KeyboardInterrupt:
        print("\nCaught KeyboardInterrupt...")

    finally:
        if args.background == False:
            try:
                # Only kill the processes if they are running, as they could
                # have been closed by the user.
                print("Killing processes...")
                dring_log.close()
                if dring_process.poll() is None:
                    dring_process.kill()
                client_log.close()
                if client_process.poll() is None:
                    client_process.kill()
            except UnboundLocalError:
                # Its okay! We crashed before we could start a process or open a
                # file. All that matters is that we close files and kill processes
                # in the right order.
                pass
Alexandre Lision's avatar
Alexandre Lision committed
366
    return True
aviau's avatar
aviau committed
367 368 369 370 371 372 373 374 375 376 377 378 379


def run_stop(args):
    execute_script(STOP_SCRIPT)


def execute_script(script, settings=None):
    if settings == None:
        settings = {}
    for line in script:
        line = line % settings
        rv = os.system(line)
        if rv != 0:
380 381
            print('Error executing script! Exit code: %s' % rv, file=sys.stderr)
            sys.exit(1)
aviau's avatar
aviau committed
382 383 384 385 386 387


def validate_args(parsed_args):
    """Validate the args values, exit if error is found"""

    # Check arg values
388
    supported_distros = [ANDROID_DISTRIBUTION_NAME, OSX_DISTRIBUTION_NAME, IOS_DISTRIBUTION_NAME] + APT_BASED_DISTROS + DNF_BASED_DISTROS + PACMAN_BASED_DISTROS + ZYPPER_BASED_DISTROS + ['mingw32','mingw64']
Simon Zeni's avatar
Simon Zeni committed
389

aviau's avatar
aviau committed
390
    if parsed_args.distribution not in supported_distros:
391
        print('Distribution \''+parsed_args.distribution+'\' not supported.\nChoose one of: %s' \
aviau's avatar
aviau committed
392 393 394 395 396 397 398 399
                  % ', '.join(supported_distros),
            file=sys.stderr)
        sys.exit(1)

def parse_args():
    ap = argparse.ArgumentParser(description="Ring build tool")

    ga = ap.add_mutually_exclusive_group(required=True)
400 401 402
    ga.add_argument(
        '--init', action='store_true',
        help='Init Ring repository')
aviau's avatar
aviau committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    ga.add_argument(
        '--dependencies', action='store_true',
        help='Install ring build dependencies')
    ga.add_argument(
        '--install', action='store_true',
        help='Build and install Ring')
    ga.add_argument(
        '--uninstall', action='store_true',
        help='Uninstall Ring')
    ga.add_argument(
        '--run', action='store_true',
         help='Run the Ring daemon and client')
    ga.add_argument(
        '--stop', action='store_true',
        help='Stop the Ring processes')

Alexandre Lision's avatar
Alexandre Lision committed
419
    ap.add_argument('--distribution', default='Automatic')
aviau's avatar
aviau committed
420 421 422 423 424 425
    ap.add_argument('--static', default=False, action='store_true')
    ap.add_argument('--global-install', default=False, action='store_true')
    ap.add_argument('--debug', default=False, action='store_true')
    ap.add_argument('--background', default=False, action='store_true')

    parsed_args = ap.parse_args()
Alexandre Lision's avatar
Alexandre Lision committed
426 427 428 429

    if parsed_args.distribution == 'Automatic':
        parsed_args.distribution = choose_distribution()

430 431 432 433 434
    if parsed_args.distribution in ['mingw32', 'mingw64']:
        if choose_distribution() != "Fedora":
            print('Windows version must be built on a Fedora distribution (>=23)')
            sys.exit(1)

aviau's avatar
aviau committed
435 436 437 438
    validate_args(parsed_args)

    return parsed_args

Alexandre Lision's avatar
Alexandre Lision committed
439 440 441
def choose_distribution():
    system = platform.system().lower()
    if system == "linux" or system == "linux2":
442 443
        if os.path.isfile("/etc/arch-release"):
            return "Arch Linux"
Alexandre Lision's avatar
Alexandre Lision committed
444 445 446 447
        with open("/etc/os-release") as f:
            for line in f:
                k,v = line.split("=")
                if k.strip() == 'NAME':
448
                    return v.strip().replace('"','').split(' ')[0]
Alexandre Lision's avatar
Alexandre Lision committed
449
    elif system == "darwin":
450
        return OSX_DISTRIBUTION_NAME
Alexandre Lision's avatar
Alexandre Lision committed
451 452 453

    return 'Unknown'

aviau's avatar
aviau committed
454 455 456 457 458 459 460

def main():
    parsed_args = parse_args()

    if parsed_args.dependencies:
        run_dependencies(parsed_args)

461 462 463
    elif parsed_args.init:
        run_init()

aviau's avatar
aviau committed
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
    elif parsed_args.install:
        run_install(parsed_args)

    elif parsed_args.uninstall:
        run_uninstall(parsed_args)

    elif parsed_args.run:
        run_run(parsed_args)

    elif parsed_args.stop:
        run_stop(parsed_args)


if __name__ == "__main__":
    main()