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 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 52
BREW_INSTALL_SCRIPT = [
    'brew update',
    'brew install -y %(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 57 58 59 60
RPM_INSTALL_SCRIPT = [
    'sudo dnf update',
    'sudo dnf install -y %(packages)s'
]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

235
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
236 237 238
        print("The Android version does not need more dependencies.\nPlease continue with the --install instruction.")
        sys.exit(1)

aviau's avatar
aviau committed
239 240 241 242
    else:
        print("Not yet implemented for current distribution (%s)" % args.distribution)
        sys.exit(1)

243
def run_init():
244 245 246 247 248 249 250
    # 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('"')])

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

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
266 267

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


def run_uninstall(args):
303
    if args.distribution == OSX_DISTRIBUTION_NAME:
Alexandre Lision's avatar
Alexandre Lision committed
304 305 306
        execute_script(OSX_UNINSTALL_SCRIPT)
    else:
        execute_script(UNINSTALL_SCRIPT)
aviau's avatar
aviau committed
307 308 309


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

aviau's avatar
aviau committed
314 315 316 317 318 319 320
    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(
321
            ["./install/daemon/lib/ring/dring", "-c", "-d"],
aviau's avatar
aviau committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
            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(
343
                ['gdb','-x', 'gdb.gdb', './install/daemon/lib/ring/dring'],
aviau's avatar
aviau committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
            )

        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
370
    return True
aviau's avatar
aviau committed
371 372 373 374 375


def run_stop(args):
    execute_script(STOP_SCRIPT)

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

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

    # Check arg values
390
    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
391

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

421
    ap.add_argument('--distribution')
aviau's avatar
aviau committed
422 423 424 425 426 427
    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
428

429 430 431
    if (parsed_args.distribution is not None):
        parsed_args.distribution = parsed_args.distribution.lower()
    else:
Alexandre Lision's avatar
Alexandre Lision committed
432 433
        parsed_args.distribution = choose_distribution()

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

aviau's avatar
aviau committed
439 440 441 442
    validate_args(parsed_args)

    return parsed_args

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

    return 'Unknown'

aviau's avatar
aviau committed
458 459 460 461 462 463 464

def main():
    parsed_args = parse_args()

    if parsed_args.dependencies:
        run_dependencies(parsed_args)

465 466 467
    elif parsed_args.init:
        run_init()

aviau's avatar
aviau committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    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()