make-ring.py 16.9 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 shlex
17
import shutil
18
import signal
aviau's avatar
aviau committed
19

20 21 22 23 24 25 26 27
IOS_DISTRIBUTION_NAME = "ios"
OSX_DISTRIBUTION_NAME = "osx"
ANDROID_DISTRIBUTION_NAME = "android"
WIN32_DISTRIBUTION_NAME = "win32"

# vs help
win_sdk_default = '10.0.16299.0'
win_toolset_default = 'v141'
28

29
APT_BASED_DISTROS = [
30 31 32 33
    'debian',
    'ubuntu',
    'linuxmint',
    'raspbian',
aviau's avatar
aviau committed
34 35
]

36
DNF_BASED_DISTROS = [
37
    'fedora', 'rhel',
38 39
]

Simon Zeni's avatar
Simon Zeni committed
40
PACMAN_BASED_DISTROS = [
41
    'arch',
Simon Zeni's avatar
Simon Zeni committed
42 43
]

44
ZYPPER_BASED_DISTROS = [
45
    'opensuse-leap',
46 47
]

48 49 50 51
FLATPAK_BASED_RUNTIMES = [
    'org.gnome.Platform',
]

aviau's avatar
aviau committed
52 53
APT_INSTALL_SCRIPT = [
    'apt-get update',
54
    'apt-get install -y %(packages)s'
aviau's avatar
aviau committed
55 56
]

57 58 59 60
BREW_UNLINK_SCRIPT = [
    'brew unlink %(packages)s'
]

Alexandre Lision's avatar
Alexandre Lision committed
61 62
BREW_INSTALL_SCRIPT = [
    'brew update',
63
    'brew install %(packages)s',
64
    'brew link --force --overwrite %(packages)s'
Alexandre Lision's avatar
Alexandre Lision committed
65
]
Simon Zeni's avatar
Simon Zeni committed
66

67
RPM_INSTALL_SCRIPT = [
68 69
    'dnf update',
    'dnf install -y %(packages)s'
70 71
]

Simon Zeni's avatar
Simon Zeni committed
72
PACMAN_INSTALL_SCRIPT = [
73 74
    'pacman -Sy',
    'pacman -S %(packages)s'
Simon Zeni's avatar
Simon Zeni committed
75 76
]

77
ZYPPER_INSTALL_SCRIPT = [
78 79
    'zypper update',
    'zypper install -y %(packages)s'
80 81
]

82
ZYPPER_DEPENDENCIES = [
83
    # build system
84
    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'patch', 'gcc-c++', 'libtool', 'which',
85
    # contrib dependencies
86
    'curl', 'gzip', 'bzip2',
87
    # daemon
88
    'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
89
    'yasm', 'libuuid-devel', 'libnettle-devel', 'libopus-devel',
90 91 92
    'libgnutls-devel', 'msgpack-devel', 'libavcodec-devel', 'libavdevice-devel', 'pcre-devel',
    'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel',
    'libopenssl-devel',
93
    # lrc
94
    'libQt5Core-devel', 'libQt5DBus-devel', 'libqt5-linguist-devel',
95
    # gnome client
96
    'gtk3-devel', 'clutter-gtk-devel', 'qrencode-devel',
97
    'gettext-tools', 'libnotify-devel', 'libappindicator3-devel', 'webkit2gtk3-devel',
98
    'NetworkManager-devel', 'libcanberra-gtk3-devel'
99 100
]

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

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

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

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

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

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

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

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

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

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


178 179 180 181 182 183 184
def run_powersell_cmd(cmd):
    p = subprocess.Popen(["powershell.exe", cmd], stdout=sys.stdout)
    p.communicate()
    p.wait()
    return


aviau's avatar
aviau committed
185
def run_dependencies(args):
186 187 188 189 190
    if(args.distribution == WIN32_DISTRIBUTION_NAME):
        run_powersell_cmd(
            'Set-ExecutionPolicy Unrestricted; .\\scripts\\build-package-windows.ps1')

    elif args.distribution in APT_BASED_DISTROS:
191 192 193 194
        execute_script(
            APT_INSTALL_SCRIPT,
            {"packages": ' '.join(map(shlex.quote, APT_DEPENDENCIES))}
        )
195

196
    elif args.distribution in DNF_BASED_DISTROS:
197 198
        execute_script(
            RPM_INSTALL_SCRIPT,
199
            {"packages": ' '.join(map(shlex.quote, DNF_DEPENDENCIES))}
200
        )
201

202
    elif args.distribution in PACMAN_BASED_DISTROS:
Simon Zeni's avatar
Simon Zeni committed
203 204
        execute_script(
            PACMAN_INSTALL_SCRIPT,
205
            {"packages": ' '.join(map(shlex.quote, PACMAN_DEPENDENCIES))}
Simon Zeni's avatar
Simon Zeni committed
206 207
        )

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

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

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

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

242 243 244 245
    elif args.distribution == WIN32_DISTRIBUTION_NAME:
        print("The win32 version does not install dependencies with this script.\nPlease continue with the --install instruction.")
        sys.exit(1)

aviau's avatar
aviau committed
246
    else:
247 248
        print("Not yet implemented for current distribution (%s)" %
              args.distribution)
aviau's avatar
aviau committed
249 250
        sys.exit(1)

251

252
def run_init():
253 254 255 256 257 258 259
    # 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('"')])

260 261
    subprocess.run(["git", "submodule", "update", "--init"], check=True)
    subprocess.run(["git", "submodule", "foreach", "git checkout master && git pull"], check=True)
262 263
    for name in module_names:
        copy_file("./scripts/commit-msg", ".git/modules/"+name+"/hooks")
264

265

266 267 268 269 270 271 272 273 274 275
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
276

277

aviau's avatar
aviau committed
278
def run_install(args):
279 280 281 282 283 284 285 286 287
    # Platforms with special compilation scripts
    if args.distribution == IOS_DISTRIBUTION_NAME:
        return subprocess.run(["./compile-ios.sh"], cwd="./client-ios", check=True)
    elif args.distribution == ANDROID_DISTRIBUTION_NAME:
        return subprocess.run(["./compile.sh"], cwd="./client-android", check=True)
    elif args.distribution == WIN32_DISTRIBUTION_NAME:
        return subprocess.run([
            sys.executable, os.path.join(os.getcwd(), "scripts/build-windows.py"),
            "--toolset", args.toolset,
288 289
            "--sdk", args.sdk,
            "--qtver", args.qtver
290 291 292 293
        ], check=True)

    # Unix-like platforms
    environ = os.environ.copy()
294

295
    install_args = ['-p', str(multiprocessing.cpu_count())]
aviau's avatar
aviau committed
296
    if args.static:
297
        install_args.append('-s')
aviau's avatar
aviau committed
298
    if args.global_install:
299
        install_args.append('-g')
300 301
    if args.prefix is not None:
        install_args += ('-P', args.prefix)
302 303
    if not args.priv_install:
        install_args.append('-u')
304

305
    if args.distribution == OSX_DISTRIBUTION_NAME:
306 307 308 309 310 311
        # The `universal_newlines` parameter has been renamed to `text` in
        # Python 3.7+ and triggering automatical binary to text conversion is
        # what it actually does
        proc = subprocess.run(["brew", "--prefix", "qt5"],
                              stdout=subprocess.PIPE, check=True,
                              universal_newlines=True)
312

313 314 315
        environ['CMAKE_PREFIX_PATH'] = proc.stdout.rstrip("\n")
        environ['CONFIGURE_FLAGS']   = '--without-dbus'
        install_args += ("-c", "client-macosx")
Alexandre Lision's avatar
Alexandre Lision committed
316
    else:
317
        if args.distribution in ZYPPER_BASED_DISTROS:
318
            # fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
319 320 321 322
            environ['JSONCPP_LIBS'] = "-ljsoncpp"
        install_args += ("-c", "client-gnome")

    return subprocess.run(["./scripts/install.sh"] + install_args, env=environ, check=True)
aviau's avatar
aviau committed
323 324 325


def run_uninstall(args):
326
    if args.distribution == OSX_DISTRIBUTION_NAME:
Alexandre Lision's avatar
Alexandre Lision committed
327 328 329
        execute_script(OSX_UNINSTALL_SCRIPT)
    else:
        execute_script(UNINSTALL_SCRIPT)
aviau's avatar
aviau committed
330 331 332


def run_run(args):
333
    if args.distribution == OSX_DISTRIBUTION_NAME:
334 335
        subprocess.Popen(
            ["install/client-macosx/Ring.app/Contents/MacOS/Ring"])
Alexandre Lision's avatar
Alexandre Lision committed
336 337
        return True

aviau's avatar
aviau committed
338
    run_env = os.environ
339 340
    run_env['LD_LIBRARY_PATH'] = run_env.get(
        'LD_LIBRARY_PATH', '') + ":install/lrc/lib"
aviau's avatar
aviau committed
341 342 343

    try:
        dring_log = open("daemon.log", 'a')
344 345
        dring_log.write('=== Starting daemon (%s) ===' %
                        time.strftime("%d/%m/%Y %H:%M:%S"))
aviau's avatar
aviau committed
346
        dring_process = subprocess.Popen(
347
            ["./install/daemon/lib/ring/dring", "-c", "-d"],
aviau's avatar
aviau committed
348 349 350 351 352 353 354
            stdout=dring_log,
            stderr=dring_log
        )

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

355
        client_log = open("jami-gnome.log", 'a')
356 357
        client_log.write('=== Starting client (%s) ===' %
                         time.strftime("%d/%m/%Y %H:%M:%S"))
aviau's avatar
aviau committed
358
        client_process = subprocess.Popen(
359
            ["./install/client-gnome/bin/jami-gnome", "-d"],
aviau's avatar
aviau committed
360 361 362 363 364
            stdout=client_log,
            stderr=client_log,
            env=run_env
        )

365
        with open('jami-gnome.pid', 'w') as f:
aviau's avatar
aviau committed
366 367 368 369
            f.write(str(client_process.pid)+'\n')

        if args.debug:
            subprocess.call(
370
                ['gdb', '-x', 'gdb.gdb', './install/daemon/lib/ring/dring'],
aviau's avatar
aviau committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
            )

        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
397
    return True
aviau's avatar
aviau committed
398 399 400 401 402


def run_stop(args):
    execute_script(STOP_SCRIPT)

403

404
def execute_script(script, settings=None, fail=True):
aviau's avatar
aviau committed
405 406 407 408 409
    if settings == None:
        settings = {}
    for line in script:
        line = line % settings
        rv = os.system(line)
410
        if rv != 0 and fail == True:
411 412
            print('Error executing script! Exit code: %s' %
                  rv, file=sys.stderr)
413
            sys.exit(1)
aviau's avatar
aviau committed
414

415

aviau's avatar
aviau committed
416 417 418 419
def validate_args(parsed_args):
    """Validate the args values, exit if error is found"""

    # Check arg values
420 421 422 423 424
    supported_distros = [
        ANDROID_DISTRIBUTION_NAME, OSX_DISTRIBUTION_NAME, IOS_DISTRIBUTION_NAME,
        WIN32_DISTRIBUTION_NAME
    ] + APT_BASED_DISTROS + DNF_BASED_DISTROS + PACMAN_BASED_DISTROS \
      + ZYPPER_BASED_DISTROS + FLATPAK_BASED_RUNTIMES
Simon Zeni's avatar
Simon Zeni committed
425

aviau's avatar
aviau committed
426
    if parsed_args.distribution not in supported_distros:
427 428 429
        print('Distribution \'{0}\' not supported.\nChoose one of: {1}'.format(
            parsed_args.distribution, ', '.join(supported_distros)
        ), file=sys.stderr)
aviau's avatar
aviau committed
430 431
        sys.exit(1)

432

aviau's avatar
aviau committed
433 434 435 436
def parse_args():
    ap = argparse.ArgumentParser(description="Ring build tool")

    ga = ap.add_mutually_exclusive_group(required=True)
437 438 439
    ga.add_argument(
        '--init', action='store_true',
        help='Init Ring repository')
aviau's avatar
aviau committed
440 441 442 443 444 445 446 447 448 449 450
    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',
451
        help='Run the Ring daemon and client')
aviau's avatar
aviau committed
452 453 454 455
    ga.add_argument(
        '--stop', action='store_true',
        help='Stop the Ring processes')

456
    ap.add_argument('--distribution')
457
    ap.add_argument('--prefix')
aviau's avatar
aviau committed
458 459 460 461
    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')
462
    ap.add_argument('--no-priv-install', dest='priv_install', default=True, action='store_false')
aviau's avatar
aviau committed
463

464 465 466
    if choose_distribution() == WIN32_DISTRIBUTION_NAME:
        ap.add_argument('--toolset', default=win_toolset_default, type=str, help='Windows use only, specify Visual Studio toolset version')
        ap.add_argument('--sdk', default=win_sdk_default, type=str, help='Windows use only, specify Windows SDK version')
467
        ap.add_argument('--qtver', default='5.9.4', help='Sets the Qt version to build with')
468

aviau's avatar
aviau committed
469
    parsed_args = ap.parse_args()
Alexandre Lision's avatar
Alexandre Lision committed
470

471 472 473
    if (parsed_args.distribution is not None):
        parsed_args.distribution = parsed_args.distribution.lower()
    else:
Alexandre Lision's avatar
Alexandre Lision committed
474 475
        parsed_args.distribution = choose_distribution()

476 477 478
    if parsed_args.distribution == WIN32_DISTRIBUTION_NAME:
        if platform.release() != '10':
            print('Windows version must be built on Windows 10')
479 480
            sys.exit(1)

aviau's avatar
aviau committed
481 482 483 484
    validate_args(parsed_args)

    return parsed_args

485

Alexandre Lision's avatar
Alexandre Lision committed
486 487
def choose_distribution():
    system = platform.system().lower()
488

Alexandre Lision's avatar
Alexandre Lision committed
489
    if system == "linux" or system == "linux2":
490
        if os.path.isfile("/etc/arch-release"):
491
            return "arch"
Alexandre Lision's avatar
Alexandre Lision committed
492 493
        with open("/etc/os-release") as f:
            for line in f:
494
                k, v = line.split("=")
495
                if k.strip() == 'ID':
496
                    return v.strip().replace('"', '').split(' ')[0]
Alexandre Lision's avatar
Alexandre Lision committed
497
    elif system == "darwin":
498
        return OSX_DISTRIBUTION_NAME
499 500
    elif system == "windows":
        return WIN32_DISTRIBUTION_NAME
Alexandre Lision's avatar
Alexandre Lision committed
501 502 503

    return 'Unknown'

aviau's avatar
aviau committed
504 505 506 507 508 509 510

def main():
    parsed_args = parse_args()

    if parsed_args.dependencies:
        run_dependencies(parsed_args)

511 512 513
    elif parsed_args.init:
        run_init()

aviau's avatar
aviau committed
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
    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()