diff options
author | Philipp Hörist <philipp@hoerist.com> | 2022-12-30 16:01:15 +0300 |
---|---|---|
committer | Philipp Hörist <philipp@hoerist.com> | 2023-01-28 14:53:14 +0300 |
commit | 76c32a5d44d7bd197ccbc9e5a6955ff2e4022066 (patch) | |
tree | 4744438e15f409cf54f97c2a91c162342427fb2d | |
parent | 4c57bd71c6665803be2c61445c90fa2295dd993a (diff) |
new: Add custom PEP517 build backend
-rw-r--r-- | MANIFEST.in | 3 | ||||
-rw-r--r-- | README.md | 80 | ||||
-rw-r--r-- | pep517build/__init__.py | 0 | ||||
-rw-r--r-- | pep517build/backend.py | 89 | ||||
-rwxr-xr-x | pep517build/build_metadata.py | 85 | ||||
-rwxr-xr-x | pep517build/install_metadata.py | 43 | ||||
-rw-r--r-- | pyproject.toml | 17 | ||||
-rwxr-xr-x | setup.py | 194 |
8 files changed, 289 insertions, 222 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 91460f387..b5c91eebc 100644 --- a/MANIFEST.in +++ b/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__ @@ -1,4 +1,6 @@ -# Welcome to Gajim +[[_TOC_]] + +## Requirements ### Runtime Requirements @@ -39,19 +41,67 @@ - [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/) (>=65.0.0) - [gettext](https://savannah.gnu.org/projects/gettext/) -### Running Tests +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. + +## Building + +### Building the metadata files (Unix only) + +```bash +./pep517build/build_metadata.py -o dist/metadata +``` + +### Building the wheel + +This is only necessary if you need the wheel, otherwise you can skip to the Installing section. + +#### Using `build` + +```bash +python -m build -w +``` + +#### Using `pip` + +```bash +pip wheel --no-deps --use-pep517 -w dist . +``` + +## Installing + +### Installing with `pip` + +```bash +pip install . +``` + +### Installing the wheel + +```bash +pip install dist/name_of_wheel.whl +``` + +### Installing the metadata files (Unix only) + +```bash +./pep517build/install_metadata.py dist/metadata --prefix=/usr +``` + +## Tests - `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 +### Packages - [Arch Linux](https://www.archlinux.org/packages/community/any/gajim/) - [Debian](https://packages.debian.org/stable/gajim) @@ -59,24 +109,20 @@ - [Ubuntu](https://packages.ubuntu.com/gajim) - [FreeBSD](https://www.freshports.org/net-im/gajim/) -#### Flatpak +### Flatpak see [README](./flatpak/README.md) -#### Snapshots +### Snapshots - [Daily Linux](https://www.gajim.org/downloads/snap/) - [Daily Windows](https://gajim.org/downloads/snap/win) -#### Linux - - pip install . - -#### Mac +### Mac see [Wiki](https://dev.gajim.org/gajim/gajim/-/wikis/help/Gajim-on-macOS) -#### Developing +## Developing To create a virtualenv you can execute @@ -89,17 +135,17 @@ Afterwards activate the virtual environment with source .venv/bin/activate ./launch.py -#### Windows +### Windows see [README](./win/README.md) -### Miscellaneous +## Miscellaneous -#### Debugging +### Debugging Execute gajim with `--verbose` -#### Links +### Links - [FAQ](https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq) - [Wiki](https://dev.gajim.org/gajim/gajim/wikis/home) diff --git a/pep517build/__init__.py b/pep517build/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pep517build/__init__.py diff --git a/pep517build/backend.py b/pep517build/backend.py new file mode 100644 index 000000000..1592ccb57 --- /dev/null +++ b/pep517build/backend.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import logging +import subprocess +from pathlib import Path + +import setuptools.build_meta as _orig + +logging.basicConfig(level='INFO', format='%(message)s') + +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) + + logging.info('Compile %s >> %s', po_file, mo_file) + + subprocess.run(['msgfmt', + str(po_file), + '-o', + str(mo_file)], + check=True) + + +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_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) + + build_translations() + + basename = _orig.build_wheel( + wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + + return basename diff --git a/pep517build/build_metadata.py b/pep517build/build_metadata.py new file mode 100755 index 000000000..7e7d53f4b --- /dev/null +++ b/pep517build/build_metadata.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import gzip +import logging +import shutil +import subprocess +from pathlib import Path + +logging.basicConfig(level='INFO', format='%(message)s') + +DEFAULT_METADATA_PATH = Path('dist/metadata') + +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_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' + logging.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('') + + logging.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 + + logging.info('Copy %s >> %s', file_path, out_path) + shutil.copy2(file_path, out_path) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Build metadata files') + parser.add_argument('-o', + '--output-dir', + type=Path, + default=DEFAULT_METADATA_PATH) + args = parser.parse_args() + + args.output_dir.mkdir(parents=True, exist_ok=True) + + build_man(args.output_dir) + build_intl(args.output_dir) + build_app_icons(args.output_dir) diff --git a/pep517build/install_metadata.py b/pep517build/install_metadata.py new file mode 100755 index 000000000..17a5a2104 --- /dev/null +++ b/pep517build/install_metadata.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import logging +import shutil +from pathlib import Path + +logging.basicConfig(level='INFO', format='%(message)s') + +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('input', + type=Path, + default=DEFAULT_METADATA_PATH, + help='Path to the metadata folder, ' + 'default: dist/metadata') + parser.add_argument('--prefix', + type=Path, + required=True, + help='The path prefix, for example "/usr"') + + args = parser.parse_args() + + for file, path in FILES.items(): + src = args.input / file + dest_dir = args.prefix / path + logging.info('Copy %s to %s', src, dest_dir) + if not dest_dir.exists(): + dest_dir.mkdir(parents=True) + shutil.copy(src, dest_dir / file) diff --git a/pyproject.toml b/pyproject.toml index 09daf3287..1b1dfebb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,8 @@ requires = [ "setuptools >= 65.0.0", ] -build-backend = "setuptools.build_meta" +build-backend = "backend" +backend-path = ["pep517build"] [project] name = "gajim" @@ -56,25 +57,18 @@ gajim-remote = "gajim.gajim_remote:main" [project.gui-scripts] gajim = "gajim.gajim:main" -[tool.setuptools] -include-package-data = true - [tool.setuptools.packages.find] include = ["gajim*"] -# Necessary so gajim.data is recogniced as namespace package and all files -# under it are included as package-data -namespaces = true [tool.setuptools.package-data] -"gajim.data" = [ - "icons/**/index.theme", -] +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] @@ -113,6 +107,7 @@ include = [ "mac/*", "scripts/*", "win/*", + "pep517build/*", "gajim/common/application.py", "gajim/common/call_manager.py", "gajim/common/cert_store.py", diff --git a/setup.py b/setup.py deleted file mode 100755 index 0cb78c633..000000000 --- a/setup.py +++ /dev/null @@ -1,194 +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, - }, - data_files=data_files -) |