mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-03-28 14:56:19 +01:00

Cf. https://review.jami.net/c/jami-daemon/+/29901 Change-Id: I46a0d1297c2e60ca43dc5524fd6e8a192bc45139
857 lines
31 KiB
Python
Executable file
857 lines
31 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# build.py --- Convenience script for building and running Jami
|
|
|
|
# Copyright (C) 2016-2025 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 signal
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import re
|
|
|
|
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',
|
|
]
|
|
|
|
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'
|
|
]
|
|
|
|
DNF_QT_WEBENGINE = ['qt6-qtwebengine-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',
|
|
'pandoc', 'nasm', 'dpkg-dev', 'libsystemd-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'
|
|
]
|
|
|
|
PACMAN_QT_WEBENGINE = ['qt6-webengine']
|
|
|
|
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'
|
|
|
|
|
|
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
|
|
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})
|
|
|
|
|
|
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:
|
|
# Build daemon if not using pywinmake
|
|
if not args.pywinmake:
|
|
with cwd('daemon/compat/msvc'):
|
|
execute_script([f'python winmake.py -iv -s {args.sdk} -b daemon'])
|
|
|
|
# Prepare the build-windows.py script call
|
|
build_windows = 'extras/scripts/build-windows.py'
|
|
# Initialize build environment
|
|
execute_script([f'python {build_windows} --init'])
|
|
|
|
# Construct build command with options
|
|
build_cmd = [
|
|
'python',
|
|
build_windows,
|
|
f'--qt={args.qt}'
|
|
]
|
|
|
|
if args.enable_crash_reports:
|
|
build_cmd.append('--enable-crash-reports')
|
|
|
|
execute_script([' '.join(build_cmd)])
|
|
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.enable_crash_reports:
|
|
install_args.append('-C')
|
|
|
|
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 \
|
|
+ ['--'] + command
|
|
|
|
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'])
|
|
|
|
|
|
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:
|
|
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'] + \
|
|
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('--arch')
|
|
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')
|
|
ap.add_argument('--enable-crash-reports',
|
|
action='store_true', default=False,
|
|
help='Enable crash reporting')
|
|
|
|
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()
|