Philipp Hörist pushed to branch port-pyproject at gajim / gajim
Commits: f45619bb by wurstsalat at 2023-01-20T23:25:10+01:00 fix: JingleAudio: Always resample audio before processing with webrtcdsp Resample both input and output in order to make sure both run at the same sampling rate in all cases.
Fixes #11023
- - - - - 59fd8466 by Daniel Brötzmann at 2023-01-24T08:18:40+00:00 chore: Update test section in Readme - - - - - 26f34d6c by Philipp Hörist at 2023-01-26T17:46:27+00:00 change: Port to pyproject.toml
- - - - - f3ed97cd by Philipp Hörist at 2023-01-26T17:46:27+00:00 new: Add custom PEP517 build backend
- - - - - 7226ba60 by Philipp Hörist at 2023-01-26T17:46:27+00:00 refactor: Load translations from package data directory
- - - - - b91e6f38 by Philipp Hörist at 2023-01-26T17:46:27+00:00 ci: Use `build` to build Gajim
- - - - - e1a912ee by Philipp Hörist at 2023-01-26T17:46:27+00:00 Update README
- - - - - 28c1ebd5 by Philipp Hörist at 2023-01-26T17:46:27+00:00 ci: Update debian control file
- - - - - 63391999 by Philipp Hörist at 2023-01-26T17:46:27+00:00 ci: Use release-helper
- - - - - ae857cf6 by Philipp Hörist at 2023-01-26T17:46:27+00:00 ci: Remove obsolete scripts
- - - - -
16 changed files:
- − .ci/appveyor_build.py - − .ci/debian_build.py - − .ci/deploy.py - .ci/prepare_git.sh - .gitlab-ci.yml - MANIFEST.in - README.md - debian/control - gajim/common/i18n.py - gajim/common/jingle_rtp.py - + pep517build/__init__.py - + pep517build/backend.py - + pep517build/install_metadata.py - pyproject.toml - − setup.cfg - − setup.py
Changes:
===================================== .ci/appveyor_build.py deleted ===================================== @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 - -from typing import Any - -import json -import logging -import os -import sys -import time -from pathlib import Path - -import requests - -ACCOUNT = 'lovetox' -PROJECT_SLUG = 'gajim' -BRANCH = 'master' -BASE_URL = 'https://ci.appveyor.com/api' -API_KEY = os.environ['APPVEYOR_API_KEY'] -HEADERS = {'Authorization': f'Bearer {API_KEY}'} -RETRY_TIMEOUT = 2 * 60 -INITIAL_START_DELAY = 20 * 60 - -SETTINGS_API_URL = f'{BASE_URL}/projects/{ACCOUNT}/{PROJECT_SLUG}/settings/yaml' -BUILDS_API_URL = f'{BASE_URL}/builds' -PROJECT_API_URL = f'{BASE_URL}/projects/{ACCOUNT}/{PROJECT_SLUG}' - - -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') -log = logging.getLogger() - - -def get_gajim_version() -> str: - if os.environ.get('GAJIM_NIGHTLY_BUILD') is not None: - return 'Nightly' - - tag = os.environ.get('CI_COMMIT_TAG') - if tag is None: - sys.exit('No tag found') - return tag - - -def push_yaml_to_project() -> None: - log.info('Push settings ...') - with open('.ci/appveyor.yml') as file: - yaml = file.read() - - req = requests.put(SETTINGS_API_URL, data=yaml, headers=HEADERS) - req.raise_for_status() - - -def start_build() -> str: - log.info('Start build ...') - payload = { - 'accountName': ACCOUNT, - 'projectSlug': PROJECT_SLUG, - 'branch': BRANCH, - 'commitId': os.environ['CI_COMMIT_SHA'], - 'environmentVariables': { - 'GAJIM_VERSION': get_gajim_version(), - } - } - req = requests.post(BUILDS_API_URL, headers=HEADERS, json=payload) - req.raise_for_status() - response = req.json() - return response['buildId'] - - -def is_build_finished(build: dict[str, str]) -> bool: - if build['status'] in ('failed', 'cancelled'): - sys.exit('Found failed job') - - return build['status'] == 'success' - - -def check_for_response(build_id: str) -> None: - time.sleep(INITIAL_START_DELAY) - while True: - time.sleep(RETRY_TIMEOUT) - - log.info('Check build status ...') - req = requests.get(PROJECT_API_URL, headers=HEADERS) - req.raise_for_status() - response = req.json() - build = response['build'] - if build_id != build['buildId']: - sys.exit('Unable to find buildid: %s' % build_id) - - if is_build_finished(build): - break - - log.info('Build status: %s', build['status']) - - build_folder = Path.cwd() / 'build' - build_folder.mkdir() - - for job in build['jobs']: - response = get_job_response(job['jobId']) - result = build_folder / f'{job["jobId"]}.json' - result.write_text(json.dumps(response)) - log.info('Write job response: %s', result) - - -def get_job_response(job_id: str) -> list[dict[str, Any]]: - artifacts_api_url = f'{BASE_URL}/buildjobs/{job_id}/artifacts' - req = requests.get(artifacts_api_url, headers=HEADERS) - req.raise_for_status() - response = req.json() - - for artifact in response: - file_url = f'{artifacts_api_url}/{artifact["fileName"]}' - artifact['fileUrl'] = file_url - - return response - - -if __name__ == '__main__': - push_yaml_to_project() - build_id = start_build() - check_for_response(build_id)
===================================== .ci/debian_build.py deleted ===================================== @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import argparse -import dataclasses -import logging -import shutil -import subprocess -from datetime import datetime -from datetime import timezone -from pathlib import Path - -LOG_FORMAT = '%(asctime)s %(levelname)s %(message)s' -logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG) -log = logging.getLogger() - -ROOT_DIR = Path(__file__).resolve().parent.parent -BUILD_DIR = ROOT_DIR / 'debian_build' - -DATE = datetime.now().strftime('%Y%m%d') -DATE_TIME = datetime.now(tz=timezone.utc).strftime('%a, %d %b %Y %T %z') - - -@dataclasses.dataclass -class ReleaseContext: - app: str - pkg_name: str - rev: str - release_name: str - release_dir: Path - tarball: Path - - @classmethod - def from_tarball(cls, path: str, prefix: str, rev: str) -> ReleaseContext: - tarball = Path(path) - app = tarball.name.split('-', maxsplit=1)[0] - pkg_name = f'{prefix}{app}-nightly' - release_name = f'{pkg_name}_{DATE}' - release_dir = BUILD_DIR / release_name - return cls(app=app, - pkg_name=pkg_name, - rev=rev, - release_name=release_name, - release_dir=release_dir, - tarball=tarball) - - -def clean_build_dir() -> None: - log.info('Cleanup build directory') - if BUILD_DIR.exists(): - shutil.rmtree(BUILD_DIR) - BUILD_DIR.mkdir() - - -def prepare_package_dir(context: ReleaseContext) -> None: - log.info('Extract tarball') - tarball = Path(shutil.copy(context.tarball, BUILD_DIR)) - tarball = tarball.rename(BUILD_DIR / f'{context.release_name}.orig.tar.gz') - shutil.unpack_archive(tarball, BUILD_DIR) - - log.info('Rename dir to: %s', context.release_name) - folder = list(BUILD_DIR.glob(f'{context.app}-?.?.?'))[0] - folder.rename(context.release_dir) - - log.info('Copy debian folder into release directory') - shutil.copytree(ROOT_DIR / 'debian', context.release_dir / 'debian') - - -def prepare_changelog(context: ReleaseContext) -> None: - log.info('Prepare Changelog') - changelog = context.release_dir / 'debian' / 'changelog' - content = changelog.read_text() - content = content.replace('{DATE}', f'{DATE}-{context.rev}') - content = content.replace('{DATE_TIME}', DATE_TIME) - changelog.write_text(content) - - -def build(context: ReleaseContext) -> None: - log.info('Start package build') - subprocess.run( - [ - 'dpkg-buildpackage', - '--no-sign' - ], - cwd=context.release_dir, - check=True) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Build debian package') - parser.add_argument('tarball', help='Path to tarball e.g. app.tar.gz') - parser.add_argument('rev', help='The package revision e.g. 1') - parser.add_argument('--pkgprefix', default='', required=False, - help='Prefix for the package name e.g. python3-') - args = parser.parse_args() - - context = ReleaseContext.from_tarball(args.tarball, - args.pkgprefix, - args.rev) - - clean_build_dir() - prepare_package_dir(context) - prepare_changelog(context) - build(context)
===================================== .ci/deploy.py deleted ===================================== @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 - -from typing import Any -from typing import cast -from typing import Optional - -import functools -import json -import logging -import os -import sys -from datetime import date -from ftplib import error_perm -from ftplib import FTP_TLS -from pathlib import Path - -import requests - -FTP_URL = 'panoramix.gajim.org' -FTP_USER = os.environ['FTP_USER'] -FTP_PASS = os.environ['FTP_PASS'] - -API_KEY = os.environ['APPVEYOR_API_KEY'] -HEADERS = {'Authorization': f'Bearer {API_KEY}'} - -WINDOWS_NIGHTLY_FOLDER = 'downloads/snap/win' -LINUX_NIGHTLY_FOLDER = 'downloads/snap' - -RELEASE_FOLDER_BASE = 'downloads' - - -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') -log = logging.getLogger() - - -def ftp_connection(func: Any) -> Any: - @functools.wraps(func) - def func_wrapper(filedir: Path) -> None: - ftp = FTP_TLS(FTP_URL, FTP_USER, FTP_PASS) - log.info('Successfully connected to %s', FTP_URL) - func(ftp, filedir) - ftp.quit() - log.info('Quit') - return - return func_wrapper - - -def get_release_folder_from_tag(tag: str) -> str: - numbers = tag.split('.') - return '.'.join(numbers[:2]) - - -def get_gajim_tag() -> str: - tag = os.environ.get('CI_COMMIT_TAG') - if tag is None: - sys.exit('No tag found') - return tag - - -def find_linux_tarball(filedir: Path) -> Path: - files = list(filedir.glob('gajim-*.tar.gz')) - if len(files) != 1: - sys.exit('Unknown files found') - return files[0] - - -def get_dir_list(ftp: FTP_TLS) -> list[str]: - return [x[0] for x in ftp.mlsd()] - - -def create_release_folder(ftp: FTP_TLS, tag: str) -> None: - ftp.cwd(RELEASE_FOLDER_BASE) - folder = get_release_folder_from_tag(tag) - try: - ftp.mkd(folder) - except error_perm: - pass - ftp.cwd(folder) - - -def upload_all_from_dir(ftp: FTP_TLS, filedir: Path) -> None: - for filepath in filedir.iterdir(): - upload_file(ftp, filepath) - - -def upload_file(ftp: FTP_TLS, - filepath: Path, - name: Optional[str] = None) -> None: - - if name is None: - name = filepath.name - - log.info('Upload file %s as %s', filepath.name, name) - with open(filepath, 'rb') as f: - ftp.storbinary('STOR ' + name, f) - - -def download_artifacts(path: Path) -> None: - build_results = list(path.glob('*.json')) - if not build_results: - sys.exit('No build build_results found') - - responses = [json.loads(response.read_text()) for response in build_results] - - for response in responses: - for artifact in response: - filename = cast(str, artifact['fileName']) - file_url = artifact['fileUrl'] - - log.info('Download %s', filename) - - req = requests.get(file_url, headers=HEADERS) - req.raise_for_status() - with open(path / filename, 'wb') as file: - file.write(req.content) - - for result in build_results: - result.unlink() - - -def get_deploy_method() -> str: - deploy_type = os.environ['DEPLOY_TYPE'] - is_nightly = bool(os.environ.get('GAJIM_NIGHTLY_BUILD')) - if is_nightly: - return f'deploy_{deploy_type}_nightly' - return f'deploy_{deploy_type}_release' - - -@ftp_connection -def deploy_windows_nightly(ftp: FTP_TLS, filedir: Path) -> None: - ftp.cwd(WINDOWS_NIGHTLY_FOLDER) - download_artifacts(filedir) - upload_all_from_dir(ftp, filedir) - - -@ftp_connection -def deploy_windows_release(ftp: FTP_TLS, filedir: Path) -> None: - tag = get_gajim_tag() - create_release_folder(ftp, tag) - download_artifacts(filedir) - upload_all_from_dir(ftp, filedir) - - -@ftp_connection -def deploy_linux_nightly(ftp: FTP_TLS, filedir: Path) -> None: - ftp.cwd(LINUX_NIGHTLY_FOLDER) - filepath = find_linux_tarball(filedir) - filename = f'gajim-{date.today().isoformat()}.tar.gz' - upload_file(ftp, filepath, name=filename) - - -@ftp_connection -def deploy_linux_release(ftp: FTP_TLS, file: Path) -> None: - tag = get_gajim_tag() - create_release_folder(ftp, tag) - filepath = find_linux_tarball(filedir) - filename = f'gajim-{tag}.tar.gz' - upload_file(ftp, filepath, name=filename) - - -if __name__ == '__main__': - filedir = Path(sys.argv[1]) - current_module = sys.modules[__name__] - method = getattr(current_module, get_deploy_method()) - method(filedir)
===================================== .ci/prepare_git.sh ===================================== @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh
echo "$2" | tr -d '\r' | ssh-add - > /dev/null ssh-add -l
===================================== .gitlab-ci.yml ===================================== @@ -2,7 +2,7 @@ image: gajim-master:latest
workflow: rules: - - if: $GAJIM_NIGHTLY_BUILD + - if: $NIGHTLY_BUILD - if: $CI_PIPELINE_SOURCE == "push"
stages: @@ -15,8 +15,9 @@ test-other: stage: test script: - codespell gajim - - python3 setup.py build - - appstream-util validate build/data/org.gajim.Gajim.appdata.xml + - pip3 install build + - python3 -m build + - appstream-util validate dist/metadata/org.gajim.Gajim.appdata.xml interruptible: true
test-pyright: @@ -67,64 +68,173 @@ test-unittest: path: coverage.xml interruptible: true
-build-linux: - image: gajim-deb-build:latest +build-unix: + image: gajim-build:latest stage: build dependencies: [] rules: - - if: '$GAJIM_NIGHTLY_BUILD' + - if: '$NIGHTLY_BUILD' - if: '$CI_COMMIT_TAG' + script: + - pip3 install build + - python3 -m build + + artifacts: + name: "gajim-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA" + expire_in: 1 week + paths: + - dist/gajim-*.tar.gz + - dist/gajim-*.whl + +build-debian-nightly: + image: gajim-build:latest + stage: build + needs: + - job: build-unix + artifacts: true + rules: + - if: '$NIGHTLY_BUILD' script: - apt-get update - apt-get install -y python3-nbxmpp-nightly - - python3 setup.py sdist - - python3 .ci/debian_build.py "$(find dist/gajim-*)" 1 + - release-helper build-debian-pkg "$(find dist/gajim-*.tar.gz)" 1 --pkgsuffix=nightly
artifacts: name: "gajim-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA" expire_in: 1 week paths: - - dist/gajim-*.tar.gz + - debian_build/*
build-windows: + image: gajim-build:latest stage: build dependencies: [] rules: - - if: '$GAJIM_NIGHTLY_BUILD' - if: '$CI_COMMIT_TAG' script: - - python3 .ci/appveyor_build.py + - > + release-helper build-windows-pkg \ + --account=lovetox \ + --slug=gajim \ + --branch=master \ + --tag=$CI_COMMIT_TAG \ + --commit-sha=$CI_COMMIT_SHA \ + --api-key=$APPVEYOR_API_KEY \ + .ci/appveyor.yml + artifacts: + expire_in: 1 day + paths: + - windows_build/*.json + +build-windows-nightly: + image: gajim-build:latest + stage: build + dependencies: [] + rules: + - if: '$NIGHTLY_BUILD' + script: + - > + release-helper build-windows-pkg \ + --account=lovetox \ + --slug=gajim \ + --branch=master \ + --tag=Nightly \ + --commit-sha=$CI_COMMIT_SHA \ + --api-key=$APPVEYOR_API_KEY \ + .ci/appveyor.yml artifacts: expire_in: 1 day paths: - - build/*.json + - windows_build/*.json
-deploy-linux: +deploy-unix: + image: gajim-deploy:latest stage: deploy dependencies: - - build-linux + - build-unix rules: - - if: '$GAJIM_NIGHTLY_BUILD' - if: '$CI_COMMIT_TAG' - variables: - DEPLOY_TYPE: "linux" script: - - python3 .ci/deploy.py dist + - > + release-helper deploy-to-ftp \ + --host=$FTP_HOST \ + --user=$FTP_USER \ + --password=$FTP_PASS \ + --directory=downloads/"$(echo $CI_COMMIT_TAG | cut -c 1-3)" \ + --rename=gajim-$CI_COMMIT_TAG.tar.gz \ + "$(find dist/gajim-*.tar.gz)" + +deploy-unix-nightly: + image: gajim-deploy:latest + stage: deploy + dependencies: + - build-unix + rules: + - if: '$NIGHTLY_BUILD' + script: + - > + release-helper deploy-to-ftp \ + --host=$FTP_HOST \ + --user=$FTP_USER \ + --password=$FTP_PASS \ + --directory=downloads/snap" \ + --rename=gajim-"$(date +'%Y-%m-%d')".tar.gz \ + "$(find dist/gajim-*.tar.gz)" + +deploy-debian-nightly: + image: gajim-deploy:latest + stage: deploy + dependencies: + - build-debian-nightly + rules: + - if: '$NIGHTLY_BUILD' + script: + - > + release-helper deploy-to-ftp \ + --host=$FTP_HOST \ + --user=$FTP_USER \ + --password=$FTP_PASS \ + --directory=debian/gajim/"$(date +'%Y%m%d')" \ + debian_build
deploy-windows: + image: gajim-deploy:latest stage: deploy dependencies: - build-windows rules: - - if: '$GAJIM_NIGHTLY_BUILD' - if: '$CI_COMMIT_TAG' - variables: - DEPLOY_TYPE: "windows" script: - - python3 .ci/deploy.py build + - release-helper download-appveyor-artifacts "$(find windows_build/*.json)" windows_build + - rm windows_build/*.json + - > + release-helper deploy-to-ftp \ + --host=$FTP_HOST \ + --user=$FTP_USER \ + --password=$FTP_PASS \ + --directory=downloads/"$(echo $CI_COMMIT_TAG | cut -c 1-3)" \ + windows_build + +deploy-windows-nightly: + image: gajim-deploy:latest + stage: deploy + dependencies: + - build-windows-nightly + rules: + - if: '$NIGHTLY_BUILD' + script: + - release-helper download-appveyor-artifacts "$(find windows_build/*.json)" windows_build + - rm windows_build/*.json + - > + release-helper deploy-to-ftp \ + --host=$FTP_HOST \ + --user=$FTP_USER \ + --password=$FTP_PASS \ + --directory=downloads/snap" \ + windows_build
deploy-flatpak: - image: git-deploy:latest + image: gajim-deploy:latest stage: deploy dependencies: [] rules: @@ -147,7 +257,7 @@ deploy-flatpak: - git push
publish-website: - image: git-deploy:latest + image: gajim-publish:latest stage: publish dependencies: [] rules: @@ -166,10 +276,10 @@ publish-website: - git push
publish-release: - image: git-deploy:latest + image: gajim-publish:latest stage: publish dependencies: [] rules: - if: '$CI_COMMIT_TAG' script: - - .ci/make_release.py $CI_COMMIT_TAG "${PROJECT_TOKEN}" + - release-helper make-gitlab-release $CI_COMMIT_TAG "${PROJECT_TOKEN}"
===================================== MANIFEST.in ===================================== @@ -1,4 +1,7 @@ include COPYING +include ChangeLog +recursive-include pep517build * recursive-include po * recursive-include data *.1 *.in recursive-include test * +prune */__pycache__
===================================== README.md ===================================== @@ -13,7 +13,7 @@ ### Runtime Requirements - [precis_i18n](https://pypi.org/project/precis-i18n/) - [packaging](https://pypi.org/project/packaging/) - [Pillow](https://pypi.org/project/Pillow/) -- [setuptools](https://pypi.org/project/setuptools/) +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0) - [Gtk3](https://gitlab.com/gnome/gtk) (>=3.24.30) - [GLib](https://gitlab.com/gnome/glib) (>=2.60.0) - [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview) @@ -39,17 +39,60 @@ #### For Video and Audio Calls - [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly) - [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
-### Install Requirements +### Build Requirements
-- [setuptools](https://pypi.org/project/setuptools/) +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0) - [gettext](https://savannah.gnu.org/projects/gettext/)
+To build Gajim a PEP517 build frontend like pip (https://pip.pypa.io/en/stable/) or build (https://pypa-build.readthedocs.io/en/stable/) must be used. + +The build frontend takes care of installing all python build requirements. Beware `gettext` is not a python library and cannot be installed by the build frontend. + +Execute the following inside the source directory + +#### Building the wheel and metadata files + +A wheel and additional metadata files are placed in the `dist` folder. + +##### Using `build` + +```bash +python -m build -w +``` + +##### Using `pip` + +```bash +pip wheel --no-deps --use-pep517 -w dist . +``` + +#### Installing + +```bash +pip install dist/name_of_wheel.whl +``` + +The wheel building step can be skipped with `pip` + +```bash +pip install . +``` + +in this case the metadata files are still placed into the `dist` directory. + +#### Installing metadata files + +On the Unix platform the metadata files need to be installed also. +There is a script to copy the files into the right places. +See `pep517build/install_metadata.py`. You may want to edit the script and +adjust the target folders depending on which distribution you use. + ### Running Tests
-- `python -m unittest discover -s test` (no_gui directory) -- `python -m unittest ./test/gtk/gui_file.py` (gtk directory) +- `python -m unittest discover -s test` +- `python -m unittest ./test/dialogs/gui_file.py` (for testing GUI files)
-### Installation Procedure +### Packages and install instructions
#### Packages
@@ -68,15 +111,11 @@ #### Snapshots - [Daily Linux](https://www.gajim.org/downloads/snap/) - [Daily Windows](https://gajim.org/downloads/snap/win)
-#### Linux - - pip install . - #### Mac
see [Wiki](https://dev.gajim.org/gajim/gajim/-/wikis/help/Gajim-on-macOS)
-#### Developing +### Developing
To create a virtualenv you can execute
===================================== debian/control ===================================== @@ -11,7 +11,7 @@ Build-Depends: python3-css-parser, python3-gi, python3-gi-cairo, - python3-nbxmpp-nightly (>=20221027), + python3-nbxmpp-nightly (>=20230123), python3-setuptools, python3-packaging, python3-cryptography (>=3.4.8), @@ -35,7 +35,7 @@ Depends: python3-gi (>= 3.42.0), python3-gi-cairo (>= 1.14.0~), python3-idna, - python3-nbxmpp-nightly (>=20221027), + python3-nbxmpp-nightly (>=20230123), python3-cryptography (>= 3.4.8), python3-precis-i18n, python3-keyring, @@ -65,6 +65,7 @@ Recommends: python3-sentry-sdk, Suggests: libxss1, +Breaks: gajim Description: Fully-featured XMPP chat client Gajim aims to be an easy to use and fully-featured XMPP client. Just chat with your friends or family, easily share pictures and
===================================== gajim/common/i18n.py ===================================== @@ -20,57 +20,18 @@
from __future__ import annotations
-from typing import Iterator +from typing import cast from typing import Optional
import gettext +import importlib.resources import locale import os import sys import unicodedata from pathlib import Path
-from gi.repository import GLib - DOMAIN = 'gajim' -_translation = gettext.NullTranslations() - - -def get_locale_dirs() -> Optional[list[Path]]: - if sys.platform == 'win32': - return - - path = gettext.find(DOMAIN) - if path is not None: - # gettext can find the location itself - # so we don’t need the localedir - return - - if Path('/app/share/run-as-flatpak').exists(): - # Check if we run as flatpak - return [Path('/app/share/')] - - data_dirs = [GLib.get_user_data_dir()] + GLib.get_system_data_dirs() - return [Path(dir_) for dir_ in data_dirs] - - -def iter_locale_dirs() -> Iterator[Optional[Path]]: - locale_dirs = get_locale_dirs() - if locale_dirs is None: - yield None - return - - # gettext fallback - locale_dirs.append(Path(sys.base_prefix) / 'share') - - found_paths: list[Path] = [] - for path in locale_dirs: - locale_dir = path / 'locale' - if locale_dir in found_paths: - continue - found_paths.append(locale_dir) - if locale_dir.is_dir(): - yield locale_dir
def get_win32_default_lang() -> str: @@ -162,19 +123,16 @@ def ngettext(s_sing: str, # doesn't retain other region settings like LC_TIME os.environ['LANGUAGE'] = LANG
-# Search for the translation in all locale dirs -for dir_ in iter_locale_dirs(): - try: - _translation = gettext.translation(DOMAIN, dir_) - _ = _translation.gettext - if hasattr(locale, 'bindtextdomain'): - locale.bindtextdomain(DOMAIN, dir_) - except OSError: - continue - else: - break - -else: + +package_dir = cast(Path, importlib.resources.files('gajim')) +locale_dir = package_dir / 'data' / 'locale' + +try: + _translation = gettext.translation(DOMAIN, locale_dir) + _ = _translation.gettext + if hasattr(locale, 'bindtextdomain'): + locale.bindtextdomain(DOMAIN, locale_dir) +except OSError: + _translation = gettext.NullTranslations() _ = _translation.gettext print('No translations found for', LANG, file=sys.stderr) - print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)
===================================== gajim/common/jingle_rtp.py ===================================== @@ -440,17 +440,24 @@ def setup_stream(self) -> None: if 'webrtcdsp' in self.available_gst_plugins: self.src_bin = self.make_bin_from_config( 'audio_input_device', - '''%s + ''' + %s + ! audioconvert + ! audioresample + ! audio/x-raw,rate=48000 ! webrtcdsp echo-suppression-level=high noise-suppression-level=very-high voice-detection=true - ! audioconvert ''', + ''', _('audio input')) else: self.src_bin = self.make_bin_from_config( 'audio_input_device', - '%s ! audioconvert ', + ''' + %s + ! audioconvert + ''', _('audio input'))
# setting name=webrtcechoprobe0 is needed because lingering probes @@ -458,13 +465,23 @@ def setup_stream(self) -> None: if 'webrtcdsp' in self.available_gst_plugins: self.sink = self.make_bin_from_config( 'audio_output_device', - '''audioconvert ! volume name=gajim_out_vol ! - webrtcechoprobe name=webrtcechoprobe0 ! %s''', + ''' + audioconvert + ! audioresample + ! audio/x-raw,rate=48000 + ! volume name=gajim_out_vol + ! webrtcechoprobe name=webrtcechoprobe0 + ! %s + ''', _('audio output')) else: self.sink = self.make_bin_from_config( 'audio_output_device', - 'audioconvert ! volume name=gajim_out_vol ! %s', + ''' + audioconvert + ! volume name=gajim_out_vol + ! %s + ''', _('audio output')) self.mic_volume = self.src_bin.get_by_name('gajim_vol') self.out_volume = self.sink.get_by_name('gajim_out_vol')
===================================== pep517build/__init__.py =====================================
===================================== pep517build/backend.py ===================================== @@ -0,0 +1,151 @@ +from __future__ import annotations + +from typing import Any + +import gzip +import logging +import shutil +import subprocess +import sys +from pathlib import Path + +import setuptools.build_meta as _orig + +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') +log = logging.getLogger() + +ALLOWED_CONFIG_SETTINGS = {'target'} +MAN_FILES = [ + Path('data/gajim.1'), + Path('data/gajim-remote.1') +] +META_FILES = [ + (Path('data/org.gajim.Gajim.desktop.in'), '--desktop'), + (Path('data/org.gajim.Gajim.appdata.xml.in'), '--xml') +] +ICONS = [ + Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg'), + Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'), +] + + +def build_translations() -> None: + # Compile translation files and place them into "gajim/data/locale" + + source_dir = Path.cwd() + translation_dir = source_dir / 'po' + locale_dir = source_dir / 'gajim' / 'data' / 'locale' + + langs = sorted([lang.stem for lang in translation_dir.glob('*.po')]) + + for lang in langs: + po_file = source_dir / 'po' / f'{lang}.po' + mo_file = locale_dir / lang / 'LC_MESSAGES' / 'gajim.mo' + mo_file.parent.mkdir(parents=True, exist_ok=True) + + log.info('Compile %s >> %s', po_file, mo_file) + + subprocess.run(['msgfmt', + str(po_file), + '-o', + str(mo_file)], + check=True) + + +def build_man(target_path: Path) -> None: + # Build man files in target path + + for man_path in MAN_FILES: + data = man_path.read_bytes() + man_file_name = man_path.name + + man_out_path = target_path / f'{man_file_name}.gz' + log.info('Compress %s >> %s', man_file_name, man_out_path) + + with gzip.open(man_out_path, 'wb') as f_out: + f_out.write(data) + + + +def build_intl(target_path: Path) -> None: + # Merge translation files into desktop and metadata files + + for file_path, option in META_FILES: + out_path = target_path / file_path.name + out_path = out_path.with_suffix('') + + log.info('Compile %s >> %s', file_path, out_path) + + subprocess.run(['msgfmt', + option, + '-d', + 'po', + '--template', + str(file_path), + '-o', + str(out_path)], + check=True) + + +def build_app_icons(target_path: Path) -> None: + for file_path in ICONS: + out_path = target_path / file_path.name + + log.info('Copy %s >> %s', file_path, out_path) + shutil.copy2(file_path, out_path) + + +def _check_config_settings(config_settings: dict[str, str]) -> None: + settings = set(config_settings.keys()) - ALLOWED_CONFIG_SETTINGS + if settings: + raise ValueError('Unknown config setting %s' % settings) + + +def _get_target_path(wheel_directory: str, + config_settings: dict[str, str] | None) -> Path: + + path = Path(wheel_directory) + if config_settings is None: + path = path / 'metadata' + else: + path = path / config_settings.get('target', 'metadata') + + path.mkdir(parents=True, exist_ok=True) + return path + + +def get_requires_for_build_sdist(*args: Any, **kwargs: Any) -> str: + return _orig.get_requires_for_build_sdist(*args, **kwargs) + +def build_sdist(*args: Any, **kwargs: Any) -> str: + return _orig.build_sdist(*args, **kwargs) + +def get_requires_for_build_wheel(*args: Any, **kwargs: Any) -> str: + return _orig.get_requires_for_build_wheel(*args, **kwargs) + +def prepare_metadata_for_build_wheel(*args: Any, **kwargs: Any) -> str: + return _orig.prepare_metadata_for_build_wheel(*args, **kwargs) + +def build_wheel(wheel_directory: str, + config_settings: dict[str, str] | None = None, + metadata_directory: str | None = None + ) -> str: + + if config_settings is not None: + _check_config_settings(config_settings) + target_path = _get_target_path(wheel_directory, config_settings) + + build_translations() + + basename = _orig.build_wheel( + wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + + if sys.platform not in ('win32', 'darwin'): + build_man(target_path) + build_intl(target_path) + build_app_icons(target_path) + + return basename
===================================== pep517build/install_metadata.py ===================================== @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +from __future__ import annotations + +import argparse +import logging +import shutil +from pathlib import Path + +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') +log = logging.getLogger() + +DEFAULT_METADATA_PATH = Path('dist/metadata') + +FILES = { + 'gajim-remote.1.gz': 'share/man/man1', + 'gajim.1.gz': 'share/man/man1', + 'org.gajim.Gajim.desktop': 'share/applications', + 'org.gajim.Gajim-symbolic.svg': 'share/icons/hicolor/scalable/apps', + 'org.gajim.Gajim.svg': 'share/icons/hicolor/scalable/apps', + 'org.gajim.Gajim.appdata.xml': 'share/metainfo', +} + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Install metadata files') + parser.add_argument('prefix', type=Path) + parser.add_argument('--path', type=Path, default=DEFAULT_METADATA_PATH) + args = parser.parse_args() + + for file, path in FILES.items(): + src = args.path / file + dest_dir = args.prefix / path + log.info('Copy %s to %s', src, dest_dir) + if not dest_dir.exists(): + dest_dir.mkdir(parents=True) + shutil.copy(src, dest_dir / file)
===================================== pyproject.toml ===================================== @@ -1,15 +1,76 @@ [build-system] requires = [ - "setuptools >= 43.0.0", + "setuptools >= 65.0.0", ] -build-backend = "setuptools.build_meta" +build-backend = "backend" +backend-path = ["pep517build"] + +[project] +name = "gajim" +description = "A GTK XMPP Client" +readme = "README.md" +requires-python = ">=3.10" +license = {text = "GPL-3.0-or-later"} +authors = [ + {name = "Philipp Hörist", email = "philipp@hoerist.com"}, + {name = "Yann Leboulanger", email = "yann@leboulanger.org"}, +] +keywords = ["chat", "messaging", "im", "xmpp", "voip"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: X11 Applications :: GTK", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.10", + "Topic :: Communications :: Chat", +] +dependencies = [ + "css-parser", + "keyring", + "nbxmpp>=3.2.5,<4.0.0", + "packaging", + "pillow", + "precis-i18n>=1.0.0", + "cryptography>=3.4.8", + "pycairo>=1.16.0", + "PyGObject>=3.42.0", +] +dynamic = ["version"] + +[project.urls] +homepage = "https://gajim.org" +repository = "https://dev.gajim.org/gajim/gajim" + +[project.optional-dependencies] +dev = [ + "ruff", + "codespell[toml]", + "nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git", + "PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git", + "python-gitlab", + "sentry-sdk", +] + +[project.scripts] +gajim-remote = "gajim.gajim_remote:main" + +[project.gui-scripts] +gajim = "gajim.gajim:main"
+[tool.setuptools.packages.find] +include = ["gajim*"] + +[tool.setuptools.package-data] +gajim = ["py.typed"] +"gajim.data" = ["**/*"] + +[tool.setuptools.dynamic] +version = {attr = "gajim.__version__"}
[tool.codespell] -skip = "*__pycache__*,build,test,*.egg-info,.git,*.po,*.nsi,*.spec" +skip = "*__pycache__*,build,dist,test,*.egg-info,.git,*.po,*.nsi,*.spec" ignore-words-list = "claus,pres,ser,trough"
- [tool.pyright] pythonVersion = "3.10" pythonPlatform = "All" @@ -46,6 +107,7 @@ include = [ "mac/*", "scripts/*", "win/*", + "pep517build/*", "gajim/common/application.py", "gajim/common/call_manager.py", "gajim/common/cert_store.py", @@ -80,7 +142,6 @@ include = [ "gajim/plugins/*", ]
- [tool.ruff] line-length = 80
@@ -164,7 +225,6 @@ target-version = "py310" "test/common/test_styling.py" = ["RUF001", "E501"] "test/common/test_regex.py" = ["RUF001"]
- [tool.ruff.mccabe] max-complexity = 15
===================================== setup.cfg deleted ===================================== @@ -1,61 +0,0 @@ -[metadata] -name = gajim -version = attr: gajim.__version__ -description = A GTK XMPP client -long_description = file: README.md -long_description_content_type = text/markdown -author = Philipp Hoerist, Yann Leboulanger -author_email = gajim-devel@gajim.org -url = https://gajim.org -license = GPL v3 -keywords = chat,messaging,im,xmpp,voip -classifiers = - Development Status :: 5 - Production/Stable - Environment :: X11 Applications :: GTK - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Operating System :: OS Independent - Programming Language :: Python :: 3.10 - Topic :: Communications :: Chat - -[options] -python_requires = >=3.10 -packages = find: -test_suite = test -install_requires = - css-parser - keyring - nbxmpp>=4.0.1,<5.0.0 - packaging - pillow - precis-i18n>=1.0.0 - cryptography>=3.4.8 - pycairo>=1.16.0 - PyGObject>=3.42.0 - -[options.extras_require] -dev = - codespell[toml] - isort - nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git - pre-commit - PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git - python-gitlab - ruamel.yaml - ruff - sentry-sdk - -[options.package_data] -gajim = - data/gui/*.ui - data/icons/hicolor/*/*/*.png - data/icons/hicolor/*/*/*.svg - data/other/* - data/sounds/*.wav - data/style/*.css - data/plugins/*/* - py.typed - -[options.packages.find] -exclude = - gajim.dev - test*
===================================== setup.py deleted ===================================== @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -from typing import cast - -import logging -import os -import sys - -if sys.version_info < (3, 10): - sys.exit('Gajim needs Python 3.10+') - -import subprocess -from pathlib import Path - -from setuptools import setup -from setuptools.command.build_py import build_py as _build -from setuptools.command.install import install as _install - -DataFilesT = list[tuple[str, list[str]]] - - -MAN_FILES = [ - 'gajim.1', - 'gajim-remote.1' -] -META_FILES = [ - ('data/org.gajim.Gajim.desktop', 'share/applications', '--desktop'), - ('data/org.gajim.Gajim.appdata.xml', 'share/metainfo', '--xml')] - - -TRANS_DIR = Path('po') -TRANS_TEMPLATE = TRANS_DIR / 'gajim.pot' -REPO_DIR = Path(__file__).resolve().parent -BUILD_DIR = REPO_DIR / 'build' - -ALL_LINGUAS = sorted([lang.stem for lang in TRANS_DIR.glob('*.po')]) - - -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s') -log = logging.getLogger() - - -def newer(source: Path, target: Path) -> bool: - if not source.exists(): - raise ValueError('file "%s" does not exist' % source.resolve()) - if not target.exists(): - return True - - from stat import ST_MTIME - mtime1 = source.stat()[ST_MTIME] - mtime2 = target.stat()[ST_MTIME] - - return mtime1 > mtime2 - - -def build_translation() -> None: - for lang in ALL_LINGUAS: - po_file = TRANS_DIR / f'{lang}.po' - mo_file = BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo' - mo_dir = mo_file.parent - if not (mo_dir.is_dir() or mo_dir.is_symlink()): - mo_dir.mkdir(parents=True) - - if newer(po_file, mo_file): - subprocess.run(['msgfmt', - str(po_file), - '-o', - str(mo_file)], - cwd=REPO_DIR, - check=True) - - log.info('Compiling %s >> %s', po_file, mo_file) - - -def install_trans(data_files: DataFilesT) -> None: - for lang in ALL_LINGUAS: - mo_file = str(BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo') - target = f'share/locale/{lang}/LC_MESSAGES' - data_files.append((target, [mo_file])) - - -def build_man() -> None: - ''' - Compress Gajim manual files - ''' - newdir = BUILD_DIR / 'man' - if not (newdir.is_dir() or newdir.is_symlink()): - newdir.mkdir() - - for man in MAN_FILES: - filename = Path('data') / man - man_file_gz = newdir / (man + '.gz') - if man_file_gz.exists(): - if newer(filename, man_file_gz): - man_file_gz.unlink() - else: - continue - - import gzip - - # Binary io, so open is OK - with open(filename, 'rb') as f_in,\ - gzip.open(man_file_gz, 'wb') as f_out: - f_out.writelines(f_in) - log.info('Compiling %s >> %s', filename, man_file_gz) - - -def install_man(data_files: DataFilesT) -> None: - man_dir = BUILD_DIR / 'man' - target = 'share/man/man1' - - for man in MAN_FILES: - man_file_gz = str(man_dir / (man + '.gz')) - data_files.append((target, [man_file_gz])) - - -def build_intl() -> None: - ''' - Merge translation files into desktop and mime files - ''' - base = BUILD_DIR - - for filename, _, option in META_FILES: - newfile = base / filename - newdir = newfile.parent - if not (newdir.is_dir() or newdir.is_symlink()): - newdir.mkdir() - merge(Path(filename + '.in'), newfile, option) - - -def install_intl(data_files: DataFilesT) -> None: - for filename, target, _ in META_FILES: - data_files.append((target, [str(BUILD_DIR / filename)])) - - -def merge(in_file: Path, - out_file: Path, - option: str, - po_dir: str = 'po') -> None: - ''' - Run the msgfmt command. - ''' - if in_file.exists(): - cmd = (('msgfmt %(opt)s -d %(po_dir)s --template %(in_file)s ' - '-o %(out_file)s') % - {'opt': option, - 'po_dir': po_dir, - 'in_file': in_file, - 'out_file': out_file}) - if os.system(cmd) != 0: - msg = ('ERROR: %s was not merged into the translation files!\n' % - out_file) - raise SystemExit(msg) - log.info('Compiling %s >> %s', in_file, out_file) - - -class Build(_build): - def run(self): - build_translation() - if sys.platform != 'win32': - build_man() - build_intl() - _build.run(self) - - -class Install(_install): - def run(self): - data_files = cast(DataFilesT, self.distribution.data_files) # pyright: ignore # noqa: E501 - install_trans(data_files) - if sys.platform != 'win32': - install_man(data_files) - install_intl(data_files) - _install.run(self) # pyright: ignore - - -# only install subdirectories of data -data_files_app_icon = [ - ('share/icons/hicolor/scalable/apps', - ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg']), - ('share/icons/hicolor/scalable/apps', - ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg']) -] - -data_files: DataFilesT = data_files_app_icon - -setup( - cmdclass={ - 'build_py': Build, - 'install': Install, - }, - entry_points={ - 'console_scripts': [ - 'gajim-remote = gajim.gajim_remote:main', - ], - 'gui_scripts': [ - 'gajim = gajim.gajim:main', - ] - }, - data_files=data_files -)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/8103832f3ec9c72798dd5bf8b5503ee7...