Newer
Older
#!/usr/bin/env python3
# build.py --- Convenience script for building and running Jami
# Copyright (C) 2016-2024 Savoir-faire Linux Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import argparse
import contextlib
import multiprocessing
import os
import platform
import shlex
import shutil
import subprocess
import sys
import time
OSX_DISTRIBUTION_NAME = "osx"
WIN32_DISTRIBUTION_NAME = "win32"
# vs vars
win_sdk_default = '10.0.18362.0'
APT_BASED_DISTROS = [
'debian',
'linuxmint',
'raspbian',
'trisquel',
'ubuntu',
]
DNF_BASED_DISTROS = [
'fedora', 'rhel', 'almalinux',
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
]
PACMAN_BASED_DISTROS = [
'arch', 'parabola',
]
ZYPPER_BASED_DISTROS = [
'opensuse-leap',
]
FLATPAK_BASED_RUNTIMES = [
'org.gnome.Platform',
]
APT_INSTALL_SCRIPT = [
'apt-get update',
'apt-get install %(packages)s'
]
BREW_UNLINK_SCRIPT = [
'brew unlink %(packages)s'
]
BREW_INSTALL_SCRIPT = [
'brew update',
'brew install %(packages)s',
'brew link --force --overwrite %(packages)s'
]
RPM_INSTALL_SCRIPT = [
'dnf update',
'dnf install %(packages)s'
]
PACMAN_INSTALL_SCRIPT = [
'pacman -Sy',
'pacman -S --asdeps --needed %(packages)s'
]
ZYPPER_INSTALL_SCRIPT = [
'zypper update',
'zypper install %(packages)s'
]
ZYPPER_DEPENDENCIES = [
# build system
'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'patch', 'gcc-c++',
'libtool', 'which', 'pandoc', 'nasm', 'doxygen', 'graphviz', 'systemd-devel',
# contrib dependencies
'curl', 'gzip', 'bzip2',
# daemon
'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
'yasm', 'libuuid-devel', 'libnettle-devel', 'libopus-devel', 'libexpat-devel',
'libgnutls-devel', 'msgpack-c-devel', 'msgpack-cxx-devel', 'libavcodec-devel', 'libavdevice-devel', 'pcre-devel',
'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel', 'pipewire-devel',
'libopenssl-devel', 'libavutil-devel',
]
ZYPPER_CLIENT_DEPENDENCIES = [
# lrc
'qt6-core-devel', 'qt6-dbus-devel', 'qt6-linguist-devel',
# client-qt
'qt6-concurrent-devel', 'qt6-qt5compat-devel', 'qt6-qt5compat-imports',
'qt6-svg-devel', 'qt6-multimedia-devel', 'qt6-multimedia-imports',
'qt6-declarative-devel', 'qt6-qmlcompiler-private-devel',
'qt6-quickcontrols2-devel', 'qt6-shadertools-devel',
'qrencode-devel', 'NetworkManager-devel'
]
ZYPPER_QT_WEBENGINE = [
'qt6-webenginecore-devel',
'qt6-webenginequick-devel',
'qt6-webenginewidgets-devel'
]
DNF_DEPENDENCIES = [
'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'speexdsp-devel', 'pulseaudio-libs-devel',
'libtool', 'dbus-devel', 'expat-devel', 'pcre-devel', 'doxygen', 'graphviz',
'yaml-cpp-devel', 'boost-devel', 'dbus-c++-devel', 'dbus-devel',
'libXext-devel', 'libXfixes-devel', 'yasm',
'speex-devel', 'chrpath', 'check', 'astyle', 'uuid-c++-devel', 'gettext-devel',
'gcc-c++', 'which', 'alsa-lib-devel', 'systemd-devel', 'libuuid-devel',
'uuid-devel', 'gnutls-devel', 'nettle-devel', 'opus-devel', 'speexdsp-devel',
'yaml-cpp-devel', 'swig', 'jsoncpp-devel',
'patch', 'libva-devel', 'openssl-devel', 'libvdpau-devel', 'pipewire-devel', 'msgpack-devel',
'sqlite-devel', 'openssl-static', 'pandoc', 'nasm',
'bzip2'
]
DNF_CLIENT_DEPENDENCIES = [
'libnotify-devel',
'qt6-qtbase-devel',
'qt6-qtsvg-devel', 'qt6-qtmultimedia-devel', 'qt6-qtdeclarative-devel',
'qrencode-devel', 'NetworkManager-libnm-devel'
]
APT_DEPENDENCIES = [
'autoconf', 'autoconf-archive', 'autopoint', 'automake', 'cmake', 'make', 'dbus', 'doxygen', 'graphviz',
'g++', 'gettext', 'libasound2-dev', 'libavcodec-dev',
'libavdevice-dev', 'libavformat-dev', 'libboost-dev',
'libcppunit-dev', 'libdbus-1-dev',
'libdbus-c++-dev', 'libebook1.2-dev', 'libexpat1-dev', 'libgnutls28-dev',
'libgtk-3-dev', 'libjack-dev',
'libopus-dev', 'libpcre3-dev', 'libpulse-dev', 'libssl-dev',
'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
'libudev-dev', 'libyaml-cpp-dev', 'sip-tester', 'swig',
'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libpipewire-0.3-dev', 'libmsgpack-dev',
]
APT_CLIENT_DEPENDENCIES = [
'qt6-base-dev', 'qt6-tools-dev', 'qt6-tools-dev-tools',
'qt6-l10n-tools', 'libnotify-dev', 'libqt6sql6-sqlite',
'libqt6core5compat6-dev', 'libqt6networkauth6-dev',
'qt6-multimedia-dev', 'libqt6svg6-dev', 'qt6-declarative-dev',
'qml6-module-qt-labs-qmlmodels',
'qml6-module-qt5compat-graphicaleffects',
'qml6-module-qtqml-workerscript',
'qml6-module-qtmultimedia',
'qml6-module-qtquick', 'qml6-module-qtquick-controls',
'qml6-module-qtquick-dialogs', 'qml6-module-qtquick-layouts',
'qml6-module-qtquick-shapes', 'qml6-module-qtquick-window',
'qml6-module-qtquick-templates', 'qml6-module-qt-labs-platform',
'libqrencode-dev', 'libnm-dev'
]
APT_QT_WEBENGINE = [
'libqt6webengine6-data', 'libqt6webenginecore6-bin',
'qt6-webengine-dev', 'qt6-webengine-dev-tools',
'qml6-module-qtwebengine', 'qml6-module-qtwebchannel']
PACMAN_DEPENDENCIES = [
'autoconf', 'autoconf-archive', 'gettext', 'cmake', 'dbus', 'doxygen', 'graphviz',
'gcc', 'ffmpeg', 'boost', 'cppunit', 'libdbus', 'dbus-c++', 'libe-book', 'expat',
'jack', 'opus', 'pcre', 'libpulse', 'speex', 'speexdsp', 'libtool', 'yaml-cpp',
'swig', 'yasm', 'make', 'patch', 'pkg-config',
'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm', 'systemd-libs'
]
PACMAN_CLIENT_DEPENDENCIES = [
# lrc
'qt6-base',
# client-qt
'qt6-declarative', 'qt6-5compat', 'qt6-multimedia',
'qt6-networkauth', 'qt6-shadertools',
'qt6-svg', 'qt6-tools',
'qrencode', 'libnm'
]
OSX_DEPENDENCIES = [
'autoconf', 'cmake', 'gettext', 'pkg-config', 'qt6',
'libtool', 'yasm', 'nasm', 'automake'
]
OSX_DEPENDENCIES_UNLINK = [
'autoconf*', 'cmake*', 'gettext*', 'pkg-config*', 'qt*', 'qt@6.*',
'libtool*', 'yasm*', 'nasm*', 'automake*', 'gnutls*', 'nettle*', 'msgpack*'
]
UNINSTALL_DAEMON_SCRIPT = [
'make -C daemon uninstall'
]
ASSUME_YES_FLAG = ' -y'
ASSUME_YES_FLAG_PACMAN = ' --noconfirm'
GUIX_MANIFEST = 'extras/packaging/gnu-linux/guix/manifest.scm'
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def run_powershell_cmd(cmd):
p = subprocess.Popen(["powershell.exe", cmd], stdout=sys.stdout)
p.communicate()
p.wait()
return
def run_dependencies(args):
if args.distribution == WIN32_DISTRIBUTION_NAME:
run_powershell_cmd(
'Set-ExecutionPolicy Unrestricted; .\\extras\\scripts\\install-deps-windows.ps1')
elif args.distribution in APT_BASED_DISTROS:
if args.assume_yes:
for i, _ in enumerate(APT_INSTALL_SCRIPT):
APT_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
execute_script(
APT_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, APT_DEPENDENCIES))})
if not args.no_webengine:
APT_CLIENT_DEPENDENCIES.extend(APT_QT_WEBENGINE)
execute_script(
APT_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, APT_CLIENT_DEPENDENCIES))})
elif args.distribution in DNF_BASED_DISTROS:
if args.assume_yes:
for i, _ in enumerate(RPM_INSTALL_SCRIPT):
RPM_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
execute_script(
RPM_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, DNF_DEPENDENCIES))})
if not args.no_webengine:
DNF_CLIENT_DEPENDENCIES.extend(DNF_QT_WEBENGINE)
execute_script(
RPM_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, DNF_CLIENT_DEPENDENCIES))})
elif args.distribution in PACMAN_BASED_DISTROS:
if args.assume_yes:
for i, _ in enumerate(PACMAN_INSTALL_SCRIPT):
PACMAN_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG_PACMAN
execute_script(
PACMAN_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, PACMAN_DEPENDENCIES))})
if not args.no_webengine:
PACMAN_CLIENT_DEPENDENCIES.extend(PACMAN_QT_WEBENGINE)
execute_script(
PACMAN_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, PACMAN_CLIENT_DEPENDENCIES))})
elif args.distribution in ZYPPER_BASED_DISTROS:
if args.assume_yes:
for i, _ in enumerate(ZYPPER_INSTALL_SCRIPT):
ZYPPER_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
execute_script(
ZYPPER_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, ZYPPER_DEPENDENCIES))})
if not args.no_webengine:
ZYPPER_CLIENT_DEPENDENCIES.extend(ZYPPER_QT_WEBENGINE)
execute_script(
ZYPPER_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, ZYPPER_CLIENT_DEPENDENCIES))})
elif args.distribution == OSX_DISTRIBUTION_NAME:
execute_script(
BREW_UNLINK_SCRIPT,
{"packages": ' '.join(map(shlex.quote, OSX_DEPENDENCIES_UNLINK))},
False
)
execute_script(
BREW_INSTALL_SCRIPT,
{"packages": ' '.join(map(shlex.quote, OSX_DEPENDENCIES))},
False
)
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)
elif args.distribution == 'guix':
print(f"Building the profile defined in '{GUIX_MANIFEST}'...")
execute_script([f'guix shell --manifest={GUIX_MANIFEST} -- true'])
else:
print("Not yet implemented for current distribution (%s). Please continue with the --install instruction. Note: You may need to install some dependencies manually." %
args.distribution)
sys.exit(1)
def run_init(args):
"""Initialize the git submodules and install the commit-msg hook."""
subprocess.run(["git", "submodule", "update", "--init"],
check=True)
client_hooks_dir = '.git/hooks'
daemon_hooks_dir = '.git/modules/daemon/hooks'
print("Installing commit-msg hooks...")
# Copy the commit-msg hook to all modules in the same way.
for hooks_dir in [client_hooks_dir, daemon_hooks_dir]:
if not os.path.exists(hooks_dir):
os.makedirs(hooks_dir)
copy_file("./extras/scripts/commit-msg",
os.path.join(hooks_dir, "commit-msg"))
print("Installing pre-commit hooks...")
format_script = "./extras/scripts/format.py"
# Prepend with the python executable if on Windows (not WSL).
if sys.platform == 'win32':
format_script = f'python {format_script}'
# The client submodule has QML files, so we need to run qmlformat on it,
# and thus need to supply the Qt path.
execute_script([f'{format_script} --install {client_hooks_dir}'
f' --qt {args.qt}'],
{"path": client_hooks_dir})
# The daemon submodule has no QML files, so we don't need to run
# qmlformat on it, and thus don't need to supply the Qt path.
execute_script([f'{format_script} --install {daemon_hooks_dir}'],
{"path": daemon_hooks_dir})
342
343
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
def copy_file(src, dest):
print(f'Copying: {src} to {dest}')
try:
shutil.copy2(src, dest)
# e.g. src and dest are the same file
except shutil.Error as e:
print(f'Error: {e}')
# e.g. source or destination doesn't exist
except IOError as e:
print(f'Error: {e.strerror}')
@contextlib.contextmanager
def cwd(path):
owd = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(owd)
def run_install(args):
# Platforms with special compilation scripts
if args.distribution == WIN32_DISTRIBUTION_NAME:
if not args.pywinmake:
with cwd('daemon/compat/msvc'):
execute_script([f'python winmake.py -iv -s {args.sdk} -b daemon'])
build_windows = 'extras/scripts/build-windows.py'
execute_script([f'python {build_windows} --init'])
execute_script([f'python {build_windows} --qt={args.qt}'])
return True
# Unix-like platforms
environ = os.environ.copy()
install_args = ['-p', str(multiprocessing.cpu_count())]
if args.static:
install_args.append('-s')
if args.global_install:
install_args.append('-g')
if args.prefix:
install_args += ('-P', args.prefix)
if not args.priv_install:
install_args.append('-u')
if args.debug:
install_args.append('-d')
if args.testing:
install_args.append('-t')
if args.asan:
install_args.append('-A')
if args.no_libwrap:
install_args.append('-W')
if args.no_webengine:
install_args.append('-w')
if args.arch:
install_args += ('-a', args.arch)
if args.extra_cmake_flags:
install_args += ('-D', args.extra_cmake_flags)
if args.distribution == OSX_DISTRIBUTION_NAME:
# 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", "qt6"],
stdout=subprocess.PIPE, check=True,
universal_newlines=True)
environ['CMAKE_PREFIX_PATH'] = proc.stdout.rstrip("\n")
environ['CONFIGURE_FLAGS'] = '--without-dbus'
if not args.qt:
raise Exception(
'provide the Qt path using --qt=/qt/install/prefix')
install_args += ("-Q", args.qt)
else:
if args.distribution in ZYPPER_BASED_DISTROS:
# fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
environ['JSONCPP_LIBS'] = "-ljsoncpp"
if args.qt:
install_args += ("-Q", args.qt)
command = ['extras/scripts/install.sh'] + install_args
if 'TARBALLS' not in os.environ:
print('info: consider setting the TARBALLS environment variable '
'to a stable writable location to avoid loosing '
'cached tarballs')
if args.distribution == 'guix':
if args.global_install:
print('error: global install is not supported when using Guix.')
sys.exit(1)
# Run the build in an isolated container.
share_tarballs_args = []
if 'TARBALLS' in os.environ:
share_tarballs_args = ['--preserve=TARBALLS',
f'--share={os.environ["TARBALLS"]}']
command = ['guix', 'shell', f'--manifest={GUIX_MANIFEST}',
'--symlink=/usr/bin/env=bin/env',
'--symlink=/etc/ssl/certs=etc/ssl/certs',
'--container', '--network'] + share_tarballs_args \
print(f'info: Building/installing using the command: {" ".join(command)}')
return subprocess.run(command, env=environ, check=True)
def run_uninstall(args):
execute_script(UNINSTALL_DAEMON_SCRIPT)
BUILD_DIR = 'build-global' if args.global_install else 'build'
if (os.path.exists(BUILD_DIR)):
UNINSTALL_CLIENT_SCRIPT = [
f'make -C {BUILD_DIR} uninstall',
f'rm -rf {BUILD_DIR}'
]
execute_script(UNINSTALL_CLIENT_SCRIPT)
def run_clean():
execute_script(['git clean -xfd',
'git submodule foreach git clean -xfd'])
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
def clean_contribs(contribs):
"""
Helper to clean one or more of the libjami contribs.
Takes a list of contrib names(space separated) to clean, or 'all' to clean all contribs.
Contribs are assumed to be in the contrib_dir: daemon/contrib
Artifacts to remove include:
- build directory: <contrib_dir>/<native_dir>/<contrib_name>
- build stamp: <contrib_dir>/<native_dir>/.<contrib_name>
- tarball: <contrib_dir>/tarballs/<contrib_name>*.tar.*
- build artifacts (we don't care about the contents of share):
- <contrib_dir>/<abi_triplet>/bin/<contrib_name>
- <contrib_dir>/<abi_triplet>/lib/<contrib_name>*
- <contrib_dir>/<abi_triplet>/include/<contrib_name>*
"""
# Not supported on Windows
if platform.system() == 'Windows':
print('Cleaning contribs is not supported on Windows. Exiting.')
sys.exit(1)
# Assume we are using the submodule here.
contrib_dir = 'daemon/contrib'
sub_dirs = os.listdir(contrib_dir)
# Let's find the abi triplet:
# The abi_triplet is 3 parts: <arch>-<vendor>-<sys> and should be the only directory
# named like that in the contrib directory. We can use a regex to find it.
triplet_pattern = re.compile(r'^[a-zA-Z0-9_]+-[a-zA-Z0-9_]+-[a-zA-Z0-9_]+$')
def is_triplet(s):
return bool(triplet_pattern.match(s))
abi_triplet = ''
for sub_dir in sub_dirs:
if is_triplet(sub_dir):
abi_triplet = sub_dir
break
# If we didn't find the abi triplet, we need to stop.
if abi_triplet == '':
print('Could not find the abi triplet for the contribs. Exiting.')
sys.exit(1)
# Let's find the native build source directory (native-*)
native_dir = ''
for sub_dir in sub_dirs:
if sub_dir.startswith('native'):
native_dir = os.path.join(contrib_dir, sub_dir)
break
# If we didn't find the native build source directory, we need to stop.
if native_dir == '':
print('Could not find the native build source directory. Exiting.')
sys.exit(1)
# If contribs is 'all', construct the list of all contribs from the contrib native directory
# list of directories only
if contribs == ['all']:
contribs = [d for d in os.listdir(native_dir) if os.path.isdir(os.path.join(native_dir, d))]
# Clean each contrib
for contrib in contribs:
print(f'Cleaning contrib: {contrib} for {abi_triplet} in {native_dir}')
build_dir = os.path.join(native_dir, contrib, '*')
build_stamp = os.path.join(native_dir, f'.{contrib}*')
tarball = os.path.join(contrib_dir, 'tarballs', f'{contrib}*.tar.*')
bins = os.path.join(contrib_dir, abi_triplet, 'bin', contrib)
libs = os.path.join(contrib_dir, abi_triplet, 'lib', f'lib{contrib}*')
includes = os.path.join(contrib_dir, abi_triplet, 'include', f'{contrib}*')
# EXCEPTIONS: pjproject and ffmpeg
if contrib == 'pjproject':
libs = f' {os.path.join(contrib_dir, abi_triplet, "lib", "libpj*")}' \
f' {os.path.join(contrib_dir, abi_triplet, "lib", "libsrtp*")}'
includes = os.path.join(contrib_dir, abi_triplet, 'include', 'pj*')
elif contrib == 'ffmpeg':
libs = f' {os.path.join(contrib_dir, abi_triplet, "lib", "libav*")}' \
f' {os.path.join(contrib_dir, abi_triplet, "lib", "libsw*")}'
includes = f' {os.path.join(contrib_dir, abi_triplet, "include", "libav*")}' \
f' {os.path.join(contrib_dir, abi_triplet, "include", "libsw*")}'
# For a dry run:
# execute_script([f'find {build_dir} {build_stamp} {tarball} {bins} {libs} {includes}'], fail=False)
execute_script([f'rm -rf {build_dir} {build_stamp} {tarball} {bins} {libs} {includes}'], fail=False)
def run_run(args):
run_env = os.environ
if args.debug:
# Ignore the interruption signal when using GDB, as it's
# common to use C-c when debugging and we do not want the
# Python script to abort the debugging session.
signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
if args.no_libwrap:
jamid_log = open("daemon.log", 'a')
jamid_log.write('=== Starting daemon (%s) ===' %
time.strftime("%d/%m/%Y %H:%M:%S"))
jamid_process = subprocess.Popen(
["./install/libexec/jamid", "-c", "-d"],
stdout=jamid_log,
stderr=jamid_log)
with open('daemon.pid', 'w') as f:
f.write(str(jamid_process.pid)+'\n')
client_log = open('jami.log', 'a')
client_log.write('=== Starting client (%s) ===' %
time.strftime("%d/%m/%Y %H:%M:%S"))
jami_cmdline = ['install/bin/jami', '-d']
if args.debug:
jami_cmdline = ['gdb', '-ex', 'run', '--args'] + jami_cmdline
print('Invoking jami with: {}'.format(str.join(' ', jami_cmdline)))
if args.debug:
print('Debugging with GDB; NOT redirecting output to log file')
client_process = subprocess.Popen(
jami_cmdline,
stdout=False if args.debug else client_log,
stderr=False if args.debug else client_log,
env=run_env)
with open('jami.pid', 'w') as f:
f.write(str(client_process.pid)+'\n')
if args.debug and args.no_libwrap:
subprocess.call(['gdb', './install/libexec/jamid'])
if not args.background:
if args.no_libwrap:
jamid_process.wait()
client_process.wait()
except KeyboardInterrupt:
print("\nCaught KeyboardInterrupt...")
finally:
if args.debug:
# Restore the default signal handler for SIGINT.
signal.signal(signal.SIGINT, signal.SIG_DFL)
if not args.background:
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
try:
# Only kill the processes if they are running, as they
# could have been closed by the user.
print("Killing processes...")
if args.no_libwrap:
jamid_log.close()
if jamid_process.poll() is None:
jamid_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
return True
def run_stop(args):
STOP_SCRIPT = ['xargs kill < jami.pid',
'xargs kill < daemon.pid']
execute_script(STOP_SCRIPT)
def execute_script(script, settings=None, fail=True):
if settings is None:
settings = {}
for line in script:
line = line % settings
rv = os.system(line)
if rv and fail:
print('Error executing script! Exit code: %s (%s)' %
(rv, script), file=sys.stderr)
sys.exit(1)
def has_guix():
"""Check whether the 'guix' command is available."""
with open(os.devnull, 'w') as f:
try:
subprocess.run(["sh", "-c", "command -v guix"],
check=True, stdout=f)
except subprocess.CalledProcessError:
return False
else:
return True
def validate_args(parsed_args):
"""Validate the args values, exit if error is found"""
# Filter unsupported distributions.
supported_distros = \
[OSX_DISTRIBUTION_NAME, WIN32_DISTRIBUTION_NAME, 'guix'] + \
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
APT_BASED_DISTROS + DNF_BASED_DISTROS + PACMAN_BASED_DISTROS \
+ ZYPPER_BASED_DISTROS + FLATPAK_BASED_RUNTIMES
if (parsed_args.distribution == 'no-check'
or 'JAMI_BUILD_NO_CHECK' in os.environ):
return
if parsed_args.distribution not in supported_distros:
print(f'WARNING: Distribution \'{parsed_args.distribution}\' is not '
f'supported. Choose one of: {", ".join(supported_distros)}. '
'Alternatively, you may force execution of this script '
'by providing the \'--distribution=no-check\' argument or by '
'exporting the JAMI_BUILD_NO_CHECK environment variable.',
file=sys.stderr)
sys.exit(1)
# On Windows, version 10 or later is needed to build Jami.
if parsed_args.distribution == WIN32_DISTRIBUTION_NAME:
if hasattr(sys, 'getwindowsversion') and sys.getwindowsversion()[0] < 10:
print('Windows 10 or later is needed to build Jami')
sys.exit(1)
def parse_args():
ap = argparse.ArgumentParser(description="Jami build tool")
ga = ap.add_mutually_exclusive_group(required=True)
ga.add_argument(
'--init', action='store_true',
help='Init Jami repository')
ga.add_argument(
'--dependencies', action='store_true',
help='Install Jami build dependencies')
ga.add_argument(
'--install', action='store_true',
help='Build and install Jami')
ga.add_argument(
'--clean', action='store_true',
help='Call "git clean" on every repository of the project'
)
ga.add_argument(
'--uninstall', action='store_true',
help='Uninstall Jami')
ga.add_argument(
'--run', action='store_true',
help='Run the Jami daemon and client')
ga.add_argument(
'--stop', action='store_true',
help='Stop the Jami processes')
ap.add_argument('--distribution')
ap.add_argument('--prefix')
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',
help='Build with debug support; run in GDB')
ap.add_argument('--asan', default=False, action='store_true',
help='Build both daemon and client with ASAN')
ap.add_argument('--background', default=False, action='store_true')
ap.add_argument('--no-priv-install', dest='priv_install',
default=True, action='store_false')
ap.add_argument('--qt', type=str,
help='Use the Qt path supplied')
ap.add_argument('--testing', dest='testing',
default=False, action='store_true',
help='Enable testing for both client and daemon')
ap.add_argument('--no-libwrap', dest='no_libwrap',
default=False, action='store_true',
help='Disable libwrap. Also set --disable-shared option to daemon configure')
ap.add_argument('-y', '--assume-yes', default=False, action='store_true',
help='Assume yes (do not prompt user) for dependency installations through the system package manager')
ap.add_argument('--no-webengine', dest='no_webengine',
default=False, action='store_true',
help='Do not use Qt WebEngine.')
ap.add_argument('--clean-contribs', nargs='+',
help='Clean the specified contribs (space separated) or \
"all" to clean all contribs before building.')
ap.add_argument('--pywinmake', dest='pywinmake',
default=False, action='store_true',
help='Build Jami for Windows using pywinmake')
# Allow supplying extra congifure flags to the client cmake.
ap.add_argument('--extra-cmake-flags', type=str,
help='Extra flags to pass to the client cmake')
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
dist = choose_distribution()
if dist == WIN32_DISTRIBUTION_NAME:
ap.add_argument('--sdk', default=win_sdk_default, type=str,
help='Windows use only, specify Windows SDK version')
parsed_args = ap.parse_args()
if parsed_args.distribution:
parsed_args.distribution = parsed_args.distribution.lower()
else:
parsed_args.distribution = dist
validate_args(parsed_args)
return parsed_args
def choose_distribution():
system = platform.system().lower()
if system == "linux" or system == "linux2":
if os.path.isfile("/etc/arch-release"):
return "arch"
try:
with open("/etc/os-release") as f:
for line in f:
k, v = line.split("=")
if k.strip() == 'ID':
return v.strip().replace('"', '').split(' ')[0]
except FileNotFoundError:
if has_guix():
return 'guix'
return 'Unknown'
elif system == "darwin":
return OSX_DISTRIBUTION_NAME
elif system == "windows":
return WIN32_DISTRIBUTION_NAME
return 'Unknown'
def main():
parsed_args = parse_args()
# Clean contribs if specified first.
if parsed_args.clean_contribs:
clean_contribs(parsed_args.clean_contribs)
if parsed_args.dependencies:
run_dependencies(parsed_args)
elif parsed_args.init:
run_init(parsed_args)
elif parsed_args.clean:
run_clean()
elif parsed_args.install:
run_install(parsed_args)
elif parsed_args.uninstall:
run_uninstall(parsed_args)
elif parsed_args.run:
if (parsed_args.distribution == 'guix'
and 'GUIX_ENVIRONMENT' not in os.environ):
# Relaunch this script, this time in a pure Guix environment.
guix_args = ['shell', '--pure',
# to allow pulseaudio to connect to an existing server
"-E", "XAUTHORITY", "-E", "XDG_RUNTIME_DIR",
f'--manifest={GUIX_MANIFEST}', '--']
args = sys.argv + ['--distribution=guix']
print('Running in a guix shell spawned with: guix {}'
.format(str.join(' ', guix_args + args)))
os.execlp('guix', 'guix', *(guix_args + args))
else:
run_run(parsed_args)
elif parsed_args.stop:
run_stop(parsed_args)
if __name__ == "__main__":
main()