make-ring.py 16.3 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 19 20
IOS_DISTRIBUTION_NAME="ios"
OSX_DISTRIBUTION_NAME="osx"
ANDROID_DISTRIBUTION_NAME="android"
21

22
APT_BASED_DISTROS = [
23 24 25 26
    'debian',
    'ubuntu',
    'linuxmint',
    'raspbian',
aviau's avatar
aviau committed
27 28
]

29
DNF_BASED_DISTROS = [
30
    'fedora',
31 32
]

Simon Zeni's avatar
Simon Zeni committed
33
PACMAN_BASED_DISTROS = [
34
    'arch',
Simon Zeni's avatar
Simon Zeni committed
35 36
]

37
ZYPPER_BASED_DISTROS = [
38
    'opensuse',
39 40
]

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

46 47 48 49
BREW_UNLINK_SCRIPT = [
    'brew unlink %(packages)s'
]

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

56
RPM_INSTALL_SCRIPT = [
57 58
    'dnf update',
    'dnf install -y %(packages)s'
59 60
]

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

66
ZYPPER_INSTALL_SCRIPT = [
67 68
    'zypper update',
    'zypper install -y %(packages)s'
69 70
]

71
ZYPPER_DEPENDENCIES = [
72
# build system
73 74 75
    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'patch', 'gcc-c++', 'libtool', 'which',
# contrib dependencies
    'curl', 'gzip', 'bzip2',
76 77
# daemon
    'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
78 79 80 81
    'libupnp-devel', 'yasm', 'libuuid-devel', 'libnettle-devel', 'libopus-devel',
    'libgnutls-devel', 'msgpack-devel', 'libavcodec-devel', 'libavdevice-devel', 'pcre-devel',
    'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel',
    'libopenssl-devel',
82 83 84
# lrc
    'libQt5Core-devel', 'libQt5DBus-devel', 'libqt5-linguist-devel',
# gnome client
85
    'gtk3-devel', 'clutter-gtk-devel', 'qrencode-devel',
86
    'gettext-tools', 'libnotify-devel', 'libappindicator3-devel', 'webkit2gtk3-devel',
87
    'NetworkManager-devel', 'libcanberra-gtk3-devel'
88 89
]

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

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

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

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

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

Alexandre Lision's avatar
Alexandre Lision committed
144
OSX_DEPENDENCIES = [
145
    'autoconf', 'cmake', 'gettext', 'pkg-config', 'qt5',
146
    'libtool', 'yasm', 'nasm', 'automake'
Alexandre Lision's avatar
Alexandre Lision committed
147 148
]

149 150
OSX_DEPENDENCIES_UNLINK = [
    'autoconf*', 'cmake*', 'gettext*', 'pkg-config*', 'qt*', 'qt@5.*',
151
    'libtool*', 'yasm*', 'nasm*', 'automake*', 'gnutls*', 'nettle*', 'msgpack*'
152 153
]

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

159 160 161 162 163
IOS_DEPENDENCIES_UNLINK = [
    'autoconf*', 'automake*', 'cmake*', 'yasm*', 'libtool*',
    'pkg-config*', 'gettext*', 'swiftlint*', 'swiftgen*'
]

aviau's avatar
aviau committed
164 165
UNINSTALL_SCRIPT = [
    'make -C daemon uninstall',
166 167 168 169
    '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
170 171
]

Alexandre Lision's avatar
Alexandre Lision committed
172 173 174 175 176
OSX_UNINSTALL_SCRIPT = [
    'make -C daemon uninstall',
    'rm -rf install/client-macosx',
]

aviau's avatar
aviau committed
177 178
STOP_SCRIPT = [
    'xargs kill < daemon.pid',
179
    'xargs kill < jami-gnome.pid',
aviau's avatar
aviau committed
180 181 182 183
]


def run_dependencies(args):
184
    if args.distribution in APT_BASED_DISTROS:
aviau's avatar
aviau committed
185
        execute_script(APT_INSTALL_SCRIPT,
186
            {"packages": ' '.join(APT_DEPENDENCIES)}
aviau's avatar
aviau committed
187
        )
188
    elif args.distribution in DNF_BASED_DISTROS:
189 190
        execute_script(
            RPM_INSTALL_SCRIPT,
191
            {"packages": ' '.join(DNF_DEPENDENCIES)}
192
        )
193 194 195 196 197 198 199 200 201 202
    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)}
        )
203
    elif args.distribution in PACMAN_BASED_DISTROS:
Simon Zeni's avatar
Simon Zeni committed
204 205
        execute_script(
            PACMAN_INSTALL_SCRIPT,
206
            {"packages": ' '.join(PACMAN_DEPENDENCIES)}
Simon Zeni's avatar
Simon Zeni committed
207 208
        )

209
    elif args.distribution in ZYPPER_BASED_DISTROS:
210 211
        execute_script(
            ZYPPER_INSTALL_SCRIPT,
212
            {"packages": ' '.join(ZYPPER_DEPENDENCIES)}
213 214
        )

215
    elif args.distribution == OSX_DISTRIBUTION_NAME:
216 217
        execute_script(
            BREW_UNLINK_SCRIPT,
218 219
            {"packages": ' '.join(OSX_DEPENDENCIES_UNLINK)},
            False
220
        )
Alexandre Lision's avatar
Alexandre Lision committed
221 222
        execute_script(
            BREW_INSTALL_SCRIPT,
223 224
            {"packages": ' '.join(OSX_DEPENDENCIES)},
            False
Alexandre Lision's avatar
Alexandre Lision committed
225 226
        )

227
    elif args.distribution == IOS_DISTRIBUTION_NAME:
228 229
        execute_script(
            BREW_UNLINK_SCRIPT,
230 231
            {"packages": ' '.join(IOS_DEPENDENCIES_UNLINK)},
            False
232
        )
233 234
        execute_script(
            BREW_INSTALL_SCRIPT,
235 236
            {"packages": ' '.join(IOS_DEPENDENCIES)},
            False
237 238
        )

239
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
240 241 242
        print("The Android version does not need more dependencies.\nPlease continue with the --install instruction.")
        sys.exit(1)

aviau's avatar
aviau committed
243 244 245 246
    else:
        print("Not yet implemented for current distribution (%s)" % args.distribution)
        sys.exit(1)

247
def run_init():
248 249 250 251 252 253 254
    # 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('"')])

255 256
    os.system("git submodule update --init")
    os.system("git submodule foreach 'git checkout master && git pull'")
257 258
    for name in module_names:
        copy_file("./scripts/commit-msg", ".git/modules/"+name+"/hooks")
259 260 261 262 263 264 265 266 267 268 269

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
270 271

def run_install(args):
272
    install_args = ' -p ' + str(multiprocessing.cpu_count())
aviau's avatar
aviau committed
273 274 275 276
    if args.static:
        install_args += ' -s'
    if args.global_install:
        install_args += ' -g'
277
    if args.distribution == OSX_DISTRIBUTION_NAME:
278
        proc= subprocess.Popen("brew --prefix qt5", shell=True, stdout=subprocess.PIPE)
Alexandre Lision's avatar
Alexandre Lision committed
279 280
        qt5dir = proc.stdout.read()
        os.environ['CMAKE_PREFIX_PATH'] = str(qt5dir.decode('ascii'))
Alexandre Lision's avatar
Alexandre Lision committed
281 282
        install_args += " -c client-macosx"
        execute_script(["CONFIGURE_FLAGS='--without-dbus' ./scripts/install.sh " + install_args])
283 284 285
    elif args.distribution == IOS_DISTRIBUTION_NAME:
        os.chdir("./client-ios")
        execute_script(["./compile-ios.sh"])
286
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
287 288
        os.chdir("./client-android")
        execute_script(["./compile.sh"])
289 290 291 292 293 294 295 296 297 298
    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
299
    else:
300
        if args.distribution in ZYPPER_BASED_DISTROS:
301
            os.environ['JSONCPP_LIBS'] = "-ljsoncpp" #fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
302
        install_args += ' -c client-gnome'
Alexandre Lision's avatar
Alexandre Lision committed
303
        execute_script(["./scripts/install.sh " + install_args])
aviau's avatar
aviau committed
304 305 306


def run_uninstall(args):
307
    if args.distribution == OSX_DISTRIBUTION_NAME:
Alexandre Lision's avatar
Alexandre Lision committed
308 309 310
        execute_script(OSX_UNINSTALL_SCRIPT)
    else:
        execute_script(UNINSTALL_SCRIPT)
aviau's avatar
aviau committed
311 312 313


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

aviau's avatar
aviau committed
318 319 320 321 322 323 324
    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(
325
            ["./install/daemon/lib/ring/dring", "-c", "-d"],
aviau's avatar
aviau committed
326 327 328 329 330 331 332
            stdout=dring_log,
            stderr=dring_log
        )

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

333
        client_log = open("jami-gnome.log", 'a')
aviau's avatar
aviau committed
334 335
        client_log.write('=== Starting client (%s) ===' % time.strftime("%d/%m/%Y %H:%M:%S"))
        client_process = subprocess.Popen(
336
            ["./install/client-gnome/bin/jami-gnome", "-d"],
aviau's avatar
aviau committed
337 338 339 340 341
            stdout=client_log,
            stderr=client_log,
            env=run_env
        )

342
        with open('jami-gnome.pid', 'w') as f:
aviau's avatar
aviau committed
343 344 345 346
            f.write(str(client_process.pid)+'\n')

        if args.debug:
            subprocess.call(
347
                ['gdb','-x', 'gdb.gdb', './install/daemon/lib/ring/dring'],
aviau's avatar
aviau committed
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
            )

        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
374
    return True
aviau's avatar
aviau committed
375 376 377 378 379


def run_stop(args):
    execute_script(STOP_SCRIPT)

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

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

    # Check arg values
394
    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
395

aviau's avatar
aviau committed
396
    if parsed_args.distribution not in supported_distros:
397
        print('Distribution \''+parsed_args.distribution+'\' not supported.\nChoose one of: %s' \
aviau's avatar
aviau committed
398 399 400 401 402 403 404 405
                  % ', '.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)
406 407 408
    ga.add_argument(
        '--init', action='store_true',
        help='Init Ring repository')
aviau's avatar
aviau committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
    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')

425
    ap.add_argument('--distribution')
aviau's avatar
aviau committed
426 427 428 429 430 431
    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
432

433 434 435
    if (parsed_args.distribution is not None):
        parsed_args.distribution = parsed_args.distribution.lower()
    else:
Alexandre Lision's avatar
Alexandre Lision committed
436 437
        parsed_args.distribution = choose_distribution()

438
    if parsed_args.distribution in ['mingw32', 'mingw64']:
439
        if choose_distribution() != "fedora":
440 441 442
            print('Windows version must be built on a Fedora distribution (>=23)')
            sys.exit(1)

aviau's avatar
aviau committed
443 444 445 446
    validate_args(parsed_args)

    return parsed_args

Alexandre Lision's avatar
Alexandre Lision committed
447 448 449
def choose_distribution():
    system = platform.system().lower()
    if system == "linux" or system == "linux2":
450
        if os.path.isfile("/etc/arch-release"):
451
            return "arch"
Alexandre Lision's avatar
Alexandre Lision committed
452 453 454
        with open("/etc/os-release") as f:
            for line in f:
                k,v = line.split("=")
455
                if k.strip() == 'ID':
456
                    return v.strip().replace('"','').split(' ')[0]
Alexandre Lision's avatar
Alexandre Lision committed
457
    elif system == "darwin":
458
        return OSX_DISTRIBUTION_NAME
Alexandre Lision's avatar
Alexandre Lision committed
459 460 461

    return 'Unknown'

aviau's avatar
aviau committed
462 463 464 465 466 467 468

def main():
    parsed_args = parse_args()

    if parsed_args.dependencies:
        run_dependencies(parsed_args)

469 470 471
    elif parsed_args.init:
        run_init()

aviau's avatar
aviau committed
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
    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()