Philipp Hörist pushed to branch port-pyproject at gajim / gajim

Commits:

16 changed files:

Changes:

  • .ci/appveyor_build.py deleted
    1
    -#!/usr/bin/env python3
    
    2
    -
    
    3
    -from typing import Any
    
    4
    -
    
    5
    -import json
    
    6
    -import logging
    
    7
    -import os
    
    8
    -import sys
    
    9
    -import time
    
    10
    -from pathlib import Path
    
    11
    -
    
    12
    -import requests
    
    13
    -
    
    14
    -ACCOUNT = 'lovetox'
    
    15
    -PROJECT_SLUG = 'gajim'
    
    16
    -BRANCH = 'master'
    
    17
    -BASE_URL = 'https://ci.appveyor.com/api'
    
    18
    -API_KEY = os.environ['APPVEYOR_API_KEY']
    
    19
    -HEADERS = {'Authorization': f'Bearer {API_KEY}'}
    
    20
    -RETRY_TIMEOUT = 2 * 60
    
    21
    -INITIAL_START_DELAY = 20 * 60
    
    22
    -
    
    23
    -SETTINGS_API_URL = f'{BASE_URL}/projects/{ACCOUNT}/{PROJECT_SLUG}/settings/yaml'
    
    24
    -BUILDS_API_URL = f'{BASE_URL}/builds'
    
    25
    -PROJECT_API_URL = f'{BASE_URL}/projects/{ACCOUNT}/{PROJECT_SLUG}'
    
    26
    -
    
    27
    -
    
    28
    -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    29
    -log = logging.getLogger()
    
    30
    -
    
    31
    -
    
    32
    -def get_gajim_version() -> str:
    
    33
    -    if os.environ.get('GAJIM_NIGHTLY_BUILD') is not None:
    
    34
    -        return 'Nightly'
    
    35
    -
    
    36
    -    tag = os.environ.get('CI_COMMIT_TAG')
    
    37
    -    if tag is None:
    
    38
    -        sys.exit('No tag found')
    
    39
    -    return tag
    
    40
    -
    
    41
    -
    
    42
    -def push_yaml_to_project() -> None:
    
    43
    -    log.info('Push settings ...')
    
    44
    -    with open('.ci/appveyor.yml') as file:
    
    45
    -        yaml = file.read()
    
    46
    -
    
    47
    -    req = requests.put(SETTINGS_API_URL, data=yaml, headers=HEADERS)
    
    48
    -    req.raise_for_status()
    
    49
    -
    
    50
    -
    
    51
    -def start_build() -> str:
    
    52
    -    log.info('Start build ...')
    
    53
    -    payload = {
    
    54
    -        'accountName': ACCOUNT,
    
    55
    -        'projectSlug': PROJECT_SLUG,
    
    56
    -        'branch': BRANCH,
    
    57
    -        'commitId': os.environ['CI_COMMIT_SHA'],
    
    58
    -        'environmentVariables': {
    
    59
    -            'GAJIM_VERSION': get_gajim_version(),
    
    60
    -        }
    
    61
    -    }
    
    62
    -    req = requests.post(BUILDS_API_URL, headers=HEADERS, json=payload)
    
    63
    -    req.raise_for_status()
    
    64
    -    response = req.json()
    
    65
    -    return response['buildId']
    
    66
    -
    
    67
    -
    
    68
    -def is_build_finished(build: dict[str, str]) -> bool:
    
    69
    -    if build['status'] in ('failed', 'cancelled'):
    
    70
    -        sys.exit('Found failed job')
    
    71
    -
    
    72
    -    return build['status'] == 'success'
    
    73
    -
    
    74
    -
    
    75
    -def check_for_response(build_id: str) -> None:
    
    76
    -    time.sleep(INITIAL_START_DELAY)
    
    77
    -    while True:
    
    78
    -        time.sleep(RETRY_TIMEOUT)
    
    79
    -
    
    80
    -        log.info('Check build status ...')
    
    81
    -        req = requests.get(PROJECT_API_URL, headers=HEADERS)
    
    82
    -        req.raise_for_status()
    
    83
    -        response = req.json()
    
    84
    -        build = response['build']
    
    85
    -        if build_id != build['buildId']:
    
    86
    -            sys.exit('Unable to find buildid: %s' % build_id)
    
    87
    -
    
    88
    -        if is_build_finished(build):
    
    89
    -            break
    
    90
    -
    
    91
    -        log.info('Build status: %s', build['status'])
    
    92
    -
    
    93
    -    build_folder = Path.cwd() / 'build'
    
    94
    -    build_folder.mkdir()
    
    95
    -
    
    96
    -    for job in build['jobs']:
    
    97
    -        response = get_job_response(job['jobId'])
    
    98
    -        result = build_folder / f'{job["jobId"]}.json'
    
    99
    -        result.write_text(json.dumps(response))
    
    100
    -        log.info('Write job response: %s', result)
    
    101
    -
    
    102
    -
    
    103
    -def get_job_response(job_id: str) -> list[dict[str, Any]]:
    
    104
    -    artifacts_api_url = f'{BASE_URL}/buildjobs/{job_id}/artifacts'
    
    105
    -    req = requests.get(artifacts_api_url, headers=HEADERS)
    
    106
    -    req.raise_for_status()
    
    107
    -    response = req.json()
    
    108
    -
    
    109
    -    for artifact in response:
    
    110
    -        file_url = f'{artifacts_api_url}/{artifact["fileName"]}'
    
    111
    -        artifact['fileUrl'] = file_url
    
    112
    -
    
    113
    -    return response
    
    114
    -
    
    115
    -
    
    116
    -if __name__ == '__main__':
    
    117
    -    push_yaml_to_project()
    
    118
    -    build_id = start_build()
    
    119
    -    check_for_response(build_id)

  • .ci/debian_build.py deleted
    1
    -#!/usr/bin/env python3
    
    2
    -
    
    3
    -from __future__ import annotations
    
    4
    -
    
    5
    -import argparse
    
    6
    -import dataclasses
    
    7
    -import logging
    
    8
    -import shutil
    
    9
    -import subprocess
    
    10
    -from datetime import datetime
    
    11
    -from datetime import timezone
    
    12
    -from pathlib import Path
    
    13
    -
    
    14
    -LOG_FORMAT = '%(asctime)s %(levelname)s %(message)s'
    
    15
    -logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)
    
    16
    -log = logging.getLogger()
    
    17
    -
    
    18
    -ROOT_DIR = Path(__file__).resolve().parent.parent
    
    19
    -BUILD_DIR = ROOT_DIR / 'debian_build'
    
    20
    -
    
    21
    -DATE = datetime.now().strftime('%Y%m%d')
    
    22
    -DATE_TIME = datetime.now(tz=timezone.utc).strftime('%a, %d %b %Y %T %z')
    
    23
    -
    
    24
    -
    
    25
    -@dataclasses.dataclass
    
    26
    -class ReleaseContext:
    
    27
    -    app: str
    
    28
    -    pkg_name: str
    
    29
    -    rev: str
    
    30
    -    release_name: str
    
    31
    -    release_dir: Path
    
    32
    -    tarball: Path
    
    33
    -
    
    34
    -    @classmethod
    
    35
    -    def from_tarball(cls, path: str, prefix: str, rev: str) -> ReleaseContext:
    
    36
    -        tarball = Path(path)
    
    37
    -        app = tarball.name.split('-', maxsplit=1)[0]
    
    38
    -        pkg_name = f'{prefix}{app}-nightly'
    
    39
    -        release_name = f'{pkg_name}_{DATE}'
    
    40
    -        release_dir = BUILD_DIR / release_name
    
    41
    -        return cls(app=app,
    
    42
    -                   pkg_name=pkg_name,
    
    43
    -                   rev=rev,
    
    44
    -                   release_name=release_name,
    
    45
    -                   release_dir=release_dir,
    
    46
    -                   tarball=tarball)
    
    47
    -
    
    48
    -
    
    49
    -def clean_build_dir() -> None:
    
    50
    -    log.info('Cleanup build directory')
    
    51
    -    if BUILD_DIR.exists():
    
    52
    -        shutil.rmtree(BUILD_DIR)
    
    53
    -    BUILD_DIR.mkdir()
    
    54
    -
    
    55
    -
    
    56
    -def prepare_package_dir(context: ReleaseContext) -> None:
    
    57
    -    log.info('Extract tarball')
    
    58
    -    tarball = Path(shutil.copy(context.tarball, BUILD_DIR))
    
    59
    -    tarball = tarball.rename(BUILD_DIR / f'{context.release_name}.orig.tar.gz')
    
    60
    -    shutil.unpack_archive(tarball, BUILD_DIR)
    
    61
    -
    
    62
    -    log.info('Rename dir to: %s', context.release_name)
    
    63
    -    folder = list(BUILD_DIR.glob(f'{context.app}-?.?.?'))[0]
    
    64
    -    folder.rename(context.release_dir)
    
    65
    -
    
    66
    -    log.info('Copy debian folder into release directory')
    
    67
    -    shutil.copytree(ROOT_DIR / 'debian', context.release_dir / 'debian')
    
    68
    -
    
    69
    -
    
    70
    -def prepare_changelog(context: ReleaseContext) -> None:
    
    71
    -    log.info('Prepare Changelog')
    
    72
    -    changelog = context.release_dir / 'debian' / 'changelog'
    
    73
    -    content = changelog.read_text()
    
    74
    -    content = content.replace('{DATE}', f'{DATE}-{context.rev}')
    
    75
    -    content = content.replace('{DATE_TIME}', DATE_TIME)
    
    76
    -    changelog.write_text(content)
    
    77
    -
    
    78
    -
    
    79
    -def build(context: ReleaseContext) -> None:
    
    80
    -    log.info('Start package build')
    
    81
    -    subprocess.run(
    
    82
    -        [
    
    83
    -            'dpkg-buildpackage',
    
    84
    -            '--no-sign'
    
    85
    -        ],
    
    86
    -        cwd=context.release_dir,
    
    87
    -        check=True)
    
    88
    -
    
    89
    -
    
    90
    -if __name__ == '__main__':
    
    91
    -    parser = argparse.ArgumentParser(description='Build debian package')
    
    92
    -    parser.add_argument('tarball', help='Path to tarball e.g. app.tar.gz')
    
    93
    -    parser.add_argument('rev', help='The package revision e.g. 1')
    
    94
    -    parser.add_argument('--pkgprefix', default='', required=False,
    
    95
    -                        help='Prefix for the package name e.g. python3-')
    
    96
    -    args = parser.parse_args()
    
    97
    -
    
    98
    -    context = ReleaseContext.from_tarball(args.tarball,
    
    99
    -                                          args.pkgprefix,
    
    100
    -                                          args.rev)
    
    101
    -
    
    102
    -    clean_build_dir()
    
    103
    -    prepare_package_dir(context)
    
    104
    -    prepare_changelog(context)
    
    105
    -    build(context)

  • .ci/deploy.py deleted
    1
    -#!/usr/bin/env python3
    
    2
    -
    
    3
    -from typing import Any
    
    4
    -from typing import cast
    
    5
    -from typing import Optional
    
    6
    -
    
    7
    -import functools
    
    8
    -import json
    
    9
    -import logging
    
    10
    -import os
    
    11
    -import sys
    
    12
    -from datetime import date
    
    13
    -from ftplib import error_perm
    
    14
    -from ftplib import FTP_TLS
    
    15
    -from pathlib import Path
    
    16
    -
    
    17
    -import requests
    
    18
    -
    
    19
    -FTP_URL = 'panoramix.gajim.org'
    
    20
    -FTP_USER = os.environ['FTP_USER']
    
    21
    -FTP_PASS = os.environ['FTP_PASS']
    
    22
    -
    
    23
    -API_KEY = os.environ['APPVEYOR_API_KEY']
    
    24
    -HEADERS = {'Authorization': f'Bearer {API_KEY}'}
    
    25
    -
    
    26
    -WINDOWS_NIGHTLY_FOLDER = 'downloads/snap/win'
    
    27
    -LINUX_NIGHTLY_FOLDER = 'downloads/snap'
    
    28
    -
    
    29
    -RELEASE_FOLDER_BASE = 'downloads'
    
    30
    -
    
    31
    -
    
    32
    -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    33
    -log = logging.getLogger()
    
    34
    -
    
    35
    -
    
    36
    -def ftp_connection(func: Any) -> Any:
    
    37
    -    @functools.wraps(func)
    
    38
    -    def func_wrapper(filedir: Path) -> None:
    
    39
    -        ftp = FTP_TLS(FTP_URL, FTP_USER, FTP_PASS)
    
    40
    -        log.info('Successfully connected to %s', FTP_URL)
    
    41
    -        func(ftp, filedir)
    
    42
    -        ftp.quit()
    
    43
    -        log.info('Quit')
    
    44
    -        return
    
    45
    -    return func_wrapper
    
    46
    -
    
    47
    -
    
    48
    -def get_release_folder_from_tag(tag: str) -> str:
    
    49
    -    numbers = tag.split('.')
    
    50
    -    return '.'.join(numbers[:2])
    
    51
    -
    
    52
    -
    
    53
    -def get_gajim_tag() -> str:
    
    54
    -    tag = os.environ.get('CI_COMMIT_TAG')
    
    55
    -    if tag is None:
    
    56
    -        sys.exit('No tag found')
    
    57
    -    return tag
    
    58
    -
    
    59
    -
    
    60
    -def find_linux_tarball(filedir: Path) -> Path:
    
    61
    -    files = list(filedir.glob('gajim-*.tar.gz'))
    
    62
    -    if len(files) != 1:
    
    63
    -        sys.exit('Unknown files found')
    
    64
    -    return files[0]
    
    65
    -
    
    66
    -
    
    67
    -def get_dir_list(ftp: FTP_TLS) -> list[str]:
    
    68
    -    return [x[0] for x in ftp.mlsd()]
    
    69
    -
    
    70
    -
    
    71
    -def create_release_folder(ftp: FTP_TLS, tag: str) -> None:
    
    72
    -    ftp.cwd(RELEASE_FOLDER_BASE)
    
    73
    -    folder = get_release_folder_from_tag(tag)
    
    74
    -    try:
    
    75
    -        ftp.mkd(folder)
    
    76
    -    except error_perm:
    
    77
    -        pass
    
    78
    -    ftp.cwd(folder)
    
    79
    -
    
    80
    -
    
    81
    -def upload_all_from_dir(ftp: FTP_TLS, filedir: Path) -> None:
    
    82
    -    for filepath in filedir.iterdir():
    
    83
    -        upload_file(ftp, filepath)
    
    84
    -
    
    85
    -
    
    86
    -def upload_file(ftp: FTP_TLS,
    
    87
    -                filepath: Path,
    
    88
    -                name: Optional[str] = None) -> None:
    
    89
    -
    
    90
    -    if name is None:
    
    91
    -        name = filepath.name
    
    92
    -
    
    93
    -    log.info('Upload file %s as %s', filepath.name, name)
    
    94
    -    with open(filepath, 'rb') as f:
    
    95
    -        ftp.storbinary('STOR ' + name, f)
    
    96
    -
    
    97
    -
    
    98
    -def download_artifacts(path: Path) -> None:
    
    99
    -    build_results = list(path.glob('*.json'))
    
    100
    -    if not build_results:
    
    101
    -        sys.exit('No build build_results found')
    
    102
    -
    
    103
    -    responses = [json.loads(response.read_text()) for response in build_results]
    
    104
    -
    
    105
    -    for response in responses:
    
    106
    -        for artifact in response:
    
    107
    -            filename = cast(str, artifact['fileName'])
    
    108
    -            file_url = artifact['fileUrl']
    
    109
    -
    
    110
    -            log.info('Download %s', filename)
    
    111
    -
    
    112
    -            req = requests.get(file_url, headers=HEADERS)
    
    113
    -            req.raise_for_status()
    
    114
    -            with open(path / filename, 'wb') as file:
    
    115
    -                file.write(req.content)
    
    116
    -
    
    117
    -    for result in build_results:
    
    118
    -        result.unlink()
    
    119
    -
    
    120
    -
    
    121
    -def get_deploy_method() -> str:
    
    122
    -    deploy_type = os.environ['DEPLOY_TYPE']
    
    123
    -    is_nightly = bool(os.environ.get('GAJIM_NIGHTLY_BUILD'))
    
    124
    -    if is_nightly:
    
    125
    -        return f'deploy_{deploy_type}_nightly'
    
    126
    -    return f'deploy_{deploy_type}_release'
    
    127
    -
    
    128
    -
    
    129
    -@ftp_connection
    
    130
    -def deploy_windows_nightly(ftp: FTP_TLS, filedir: Path) -> None:
    
    131
    -    ftp.cwd(WINDOWS_NIGHTLY_FOLDER)
    
    132
    -    download_artifacts(filedir)
    
    133
    -    upload_all_from_dir(ftp, filedir)
    
    134
    -
    
    135
    -
    
    136
    -@ftp_connection
    
    137
    -def deploy_windows_release(ftp: FTP_TLS, filedir: Path) -> None:
    
    138
    -    tag = get_gajim_tag()
    
    139
    -    create_release_folder(ftp, tag)
    
    140
    -    download_artifacts(filedir)
    
    141
    -    upload_all_from_dir(ftp, filedir)
    
    142
    -
    
    143
    -
    
    144
    -@ftp_connection
    
    145
    -def deploy_linux_nightly(ftp: FTP_TLS, filedir: Path) -> None:
    
    146
    -    ftp.cwd(LINUX_NIGHTLY_FOLDER)
    
    147
    -    filepath = find_linux_tarball(filedir)
    
    148
    -    filename = f'gajim-{date.today().isoformat()}.tar.gz'
    
    149
    -    upload_file(ftp, filepath, name=filename)
    
    150
    -
    
    151
    -
    
    152
    -@ftp_connection
    
    153
    -def deploy_linux_release(ftp: FTP_TLS, file: Path) -> None:
    
    154
    -    tag = get_gajim_tag()
    
    155
    -    create_release_folder(ftp, tag)
    
    156
    -    filepath = find_linux_tarball(filedir)
    
    157
    -    filename = f'gajim-{tag}.tar.gz'
    
    158
    -    upload_file(ftp, filepath, name=filename)
    
    159
    -
    
    160
    -
    
    161
    -if __name__ == '__main__':
    
    162
    -    filedir = Path(sys.argv[1])
    
    163
    -    current_module = sys.modules[__name__]
    
    164
    -    method = getattr(current_module, get_deploy_method())
    
    165
    -    method(filedir)

  • .ci/prepare_git.sh
    1
    -#!/bin/bash
    
    1
    +#!/bin/sh
    
    2 2
     
    
    3 3
     echo "$2" | tr -d '\r' | ssh-add - > /dev/null
    
    4 4
     ssh-add -l
    

  • .gitlab-ci.yml
    ... ... @@ -2,7 +2,7 @@ image: gajim-master:latest
    2 2
     
    
    3 3
     workflow:
    
    4 4
       rules:
    
    5
    -    - if: $GAJIM_NIGHTLY_BUILD
    
    5
    +    - if: $NIGHTLY_BUILD
    
    6 6
         - if: $CI_PIPELINE_SOURCE == "push"
    
    7 7
     
    
    8 8
     stages:
    
    ... ... @@ -15,8 +15,9 @@ test-other:
    15 15
       stage: test
    
    16 16
       script:
    
    17 17
         - codespell gajim
    
    18
    -    - python3 setup.py build
    
    19
    -    - appstream-util validate build/data/org.gajim.Gajim.appdata.xml
    
    18
    +    - pip3 install build
    
    19
    +    - python3 -m build
    
    20
    +    - appstream-util validate dist/metadata/org.gajim.Gajim.appdata.xml
    
    20 21
       interruptible: true
    
    21 22
     
    
    22 23
     test-pyright:
    
    ... ... @@ -67,64 +68,173 @@ test-unittest:
    67 68
             path: coverage.xml
    
    68 69
       interruptible: true
    
    69 70
     
    
    70
    -build-linux:
    
    71
    -  image: gajim-deb-build:latest
    
    71
    +build-unix:
    
    72
    +  image: gajim-build:latest
    
    72 73
       stage: build
    
    73 74
       dependencies: []
    
    74 75
       rules:
    
    75
    -    - if: '$GAJIM_NIGHTLY_BUILD'
    
    76
    +    - if: '$NIGHTLY_BUILD'
    
    76 77
         - if: '$CI_COMMIT_TAG'
    
    78
    +  script:
    
    79
    +    - pip3 install build
    
    80
    +    - python3 -m build
    
    81
    +
    
    82
    +  artifacts:
    
    83
    +    name: "gajim-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA"
    
    84
    +    expire_in: 1 week
    
    85
    +    paths:
    
    86
    +      - dist/gajim-*.tar.gz
    
    87
    +      - dist/gajim-*.whl
    
    88
    +
    
    89
    +build-debian-nightly:
    
    90
    +  image: gajim-build:latest
    
    91
    +  stage: build
    
    92
    +  needs:
    
    93
    +    - job: build-unix
    
    94
    +      artifacts: true
    
    95
    +  rules:
    
    96
    +    - if: '$NIGHTLY_BUILD'
    
    77 97
       script:
    
    78 98
         - apt-get update
    
    79 99
         - apt-get install -y python3-nbxmpp-nightly
    
    80
    -    - python3 setup.py sdist
    
    81
    -    - python3 .ci/debian_build.py "$(find dist/gajim-*)" 1
    
    100
    +    - release-helper build-debian-pkg "$(find dist/gajim-*.tar.gz)" 1 --pkgsuffix=nightly
    
    82 101
     
    
    83 102
       artifacts:
    
    84 103
         name: "gajim-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA"
    
    85 104
         expire_in: 1 week
    
    86 105
         paths:
    
    87
    -      - dist/gajim-*.tar.gz
    
    106
    +      - debian_build/*
    
    88 107
     
    
    89 108
     build-windows:
    
    109
    +  image: gajim-build:latest
    
    90 110
       stage: build
    
    91 111
       dependencies: []
    
    92 112
       rules:
    
    93
    -    - if: '$GAJIM_NIGHTLY_BUILD'
    
    94 113
         - if: '$CI_COMMIT_TAG'
    
    95 114
       script:
    
    96
    -    - python3 .ci/appveyor_build.py
    
    115
    +    - >
    
    116
    +      release-helper build-windows-pkg \
    
    117
    +        --account=lovetox \
    
    118
    +        --slug=gajim \
    
    119
    +        --branch=master \
    
    120
    +        --tag=$CI_COMMIT_TAG \
    
    121
    +        --commit-sha=$CI_COMMIT_SHA \
    
    122
    +        --api-key=$APPVEYOR_API_KEY \
    
    123
    +        .ci/appveyor.yml
    
    124
    +  artifacts:
    
    125
    +    expire_in: 1 day
    
    126
    +    paths:
    
    127
    +      - windows_build/*.json
    
    128
    +
    
    129
    +build-windows-nightly:
    
    130
    +  image: gajim-build:latest
    
    131
    +  stage: build
    
    132
    +  dependencies: []
    
    133
    +  rules:
    
    134
    +    - if: '$NIGHTLY_BUILD'
    
    135
    +  script:
    
    136
    +    - >
    
    137
    +      release-helper build-windows-pkg \
    
    138
    +        --account=lovetox \
    
    139
    +        --slug=gajim \
    
    140
    +        --branch=master \
    
    141
    +        --tag=Nightly \
    
    142
    +        --commit-sha=$CI_COMMIT_SHA \
    
    143
    +        --api-key=$APPVEYOR_API_KEY \
    
    144
    +        .ci/appveyor.yml
    
    97 145
       artifacts:
    
    98 146
         expire_in: 1 day
    
    99 147
         paths:
    
    100
    -      - build/*.json
    
    148
    +      - windows_build/*.json
    
    101 149
     
    
    102
    -deploy-linux:
    
    150
    +deploy-unix:
    
    151
    +  image: gajim-deploy:latest
    
    103 152
       stage: deploy
    
    104 153
       dependencies:
    
    105
    -    - build-linux
    
    154
    +    - build-unix
    
    106 155
       rules:
    
    107
    -    - if: '$GAJIM_NIGHTLY_BUILD'
    
    108 156
         - if: '$CI_COMMIT_TAG'
    
    109
    -  variables:
    
    110
    -    DEPLOY_TYPE: "linux"
    
    111 157
       script:
    
    112
    -    - python3 .ci/deploy.py dist
    
    158
    +    - >
    
    159
    +      release-helper deploy-to-ftp \
    
    160
    +        --host=$FTP_HOST \
    
    161
    +        --user=$FTP_USER \
    
    162
    +        --password=$FTP_PASS \
    
    163
    +        --directory=downloads/"$(echo $CI_COMMIT_TAG | cut -c 1-3)" \
    
    164
    +        --rename=gajim-$CI_COMMIT_TAG.tar.gz \
    
    165
    +        "$(find dist/gajim-*.tar.gz)"
    
    166
    +
    
    167
    +deploy-unix-nightly:
    
    168
    +  image: gajim-deploy:latest
    
    169
    +  stage: deploy
    
    170
    +  dependencies:
    
    171
    +    - build-unix
    
    172
    +  rules:
    
    173
    +    - if: '$NIGHTLY_BUILD'
    
    174
    +  script:
    
    175
    +    - >
    
    176
    +      release-helper deploy-to-ftp \
    
    177
    +        --host=$FTP_HOST \
    
    178
    +        --user=$FTP_USER \
    
    179
    +        --password=$FTP_PASS \
    
    180
    +        --directory=downloads/snap" \
    
    181
    +        --rename=gajim-"$(date +'%Y-%m-%d')".tar.gz \
    
    182
    +        "$(find dist/gajim-*.tar.gz)"
    
    183
    +
    
    184
    +deploy-debian-nightly:
    
    185
    +  image: gajim-deploy:latest
    
    186
    +  stage: deploy
    
    187
    +  dependencies:
    
    188
    +    - build-debian-nightly
    
    189
    +  rules:
    
    190
    +    - if: '$NIGHTLY_BUILD'
    
    191
    +  script:
    
    192
    +    - >
    
    193
    +      release-helper deploy-to-ftp \
    
    194
    +        --host=$FTP_HOST \
    
    195
    +        --user=$FTP_USER \
    
    196
    +        --password=$FTP_PASS \
    
    197
    +        --directory=debian/gajim/"$(date +'%Y%m%d')" \
    
    198
    +        debian_build
    
    113 199
     
    
    114 200
     deploy-windows:
    
    201
    +  image: gajim-deploy:latest
    
    115 202
       stage: deploy
    
    116 203
       dependencies:
    
    117 204
         - build-windows
    
    118 205
       rules:
    
    119
    -    - if: '$GAJIM_NIGHTLY_BUILD'
    
    120 206
         - if: '$CI_COMMIT_TAG'
    
    121
    -  variables:
    
    122
    -    DEPLOY_TYPE: "windows"
    
    123 207
       script:
    
    124
    -    - python3 .ci/deploy.py build
    
    208
    +    - release-helper download-appveyor-artifacts "$(find windows_build/*.json)" windows_build
    
    209
    +    - rm windows_build/*.json
    
    210
    +    - >
    
    211
    +      release-helper deploy-to-ftp \
    
    212
    +        --host=$FTP_HOST \
    
    213
    +        --user=$FTP_USER \
    
    214
    +        --password=$FTP_PASS \
    
    215
    +        --directory=downloads/"$(echo $CI_COMMIT_TAG | cut -c 1-3)" \
    
    216
    +        windows_build
    
    217
    +
    
    218
    +deploy-windows-nightly:
    
    219
    +  image: gajim-deploy:latest
    
    220
    +  stage: deploy
    
    221
    +  dependencies:
    
    222
    +    - build-windows-nightly
    
    223
    +  rules:
    
    224
    +    - if: '$NIGHTLY_BUILD'
    
    225
    +  script:
    
    226
    +    - release-helper download-appveyor-artifacts "$(find windows_build/*.json)" windows_build
    
    227
    +    - rm windows_build/*.json
    
    228
    +    - >
    
    229
    +      release-helper deploy-to-ftp \
    
    230
    +        --host=$FTP_HOST \
    
    231
    +        --user=$FTP_USER \
    
    232
    +        --password=$FTP_PASS \
    
    233
    +        --directory=downloads/snap" \
    
    234
    +        windows_build
    
    125 235
     
    
    126 236
     deploy-flatpak:
    
    127
    -  image: git-deploy:latest
    
    237
    +  image: gajim-deploy:latest
    
    128 238
       stage: deploy
    
    129 239
       dependencies: []
    
    130 240
       rules:
    
    ... ... @@ -147,7 +257,7 @@ deploy-flatpak:
    147 257
         - git push
    
    148 258
     
    
    149 259
     publish-website:
    
    150
    -  image: git-deploy:latest
    
    260
    +  image: gajim-publish:latest
    
    151 261
       stage: publish
    
    152 262
       dependencies: []
    
    153 263
       rules:
    
    ... ... @@ -166,10 +276,10 @@ publish-website:
    166 276
         - git push
    
    167 277
     
    
    168 278
     publish-release:
    
    169
    -  image: git-deploy:latest
    
    279
    +  image: gajim-publish:latest
    
    170 280
       stage: publish
    
    171 281
       dependencies: []
    
    172 282
       rules:
    
    173 283
         - if: '$CI_COMMIT_TAG'
    
    174 284
       script:
    
    175
    -    - .ci/make_release.py $CI_COMMIT_TAG "${PROJECT_TOKEN}"
    285
    +    - release-helper make-gitlab-release $CI_COMMIT_TAG "${PROJECT_TOKEN}"

  • MANIFEST.in
    1 1
     include COPYING
    
    2
    +include ChangeLog
    
    3
    +recursive-include pep517build *
    
    2 4
     recursive-include po *
    
    3 5
     recursive-include data *.1 *.in
    
    4 6
     recursive-include test *
    
    7
    +prune */__pycache__

  • README.md
    ... ... @@ -13,7 +13,7 @@ ### Runtime Requirements
    13 13
     - [precis_i18n](https://pypi.org/project/precis-i18n/)
    
    14 14
     - [packaging](https://pypi.org/project/packaging/)
    
    15 15
     - [Pillow](https://pypi.org/project/Pillow/)
    
    16
    -- [setuptools](https://pypi.org/project/setuptools/)
    
    16
    +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0)
    
    17 17
     - [Gtk3](https://gitlab.com/gnome/gtk) (>=3.24.30)
    
    18 18
     - [GLib](https://gitlab.com/gnome/glib) (>=2.60.0)
    
    19 19
     - [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview)
    
    ... ... @@ -39,17 +39,60 @@ #### For Video and Audio Calls
    39 39
     - [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly)
    
    40 40
     - [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
    
    41 41
     
    
    42
    -### Install Requirements
    
    42
    +### Build Requirements
    
    43 43
     
    
    44
    -- [setuptools](https://pypi.org/project/setuptools/)
    
    44
    +- [setuptools](https://pypi.org/project/setuptools/) (>=65.0.0)
    
    45 45
     - [gettext](https://savannah.gnu.org/projects/gettext/)
    
    46 46
     
    
    47
    +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.
    
    48
    +
    
    49
    +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.
    
    50
    +
    
    51
    +Execute the following inside the source directory
    
    52
    +
    
    53
    +#### Building the wheel and metadata files
    
    54
    +
    
    55
    +A wheel and additional metadata files are placed in the `dist` folder.
    
    56
    +
    
    57
    +##### Using `build`
    
    58
    +
    
    59
    +```bash
    
    60
    +python -m build -w
    
    61
    +```
    
    62
    +
    
    63
    +##### Using `pip`
    
    64
    +
    
    65
    +```bash
    
    66
    +pip wheel --no-deps --use-pep517 -w dist .
    
    67
    +```
    
    68
    +
    
    69
    +#### Installing
    
    70
    +
    
    71
    +```bash
    
    72
    +pip install dist/name_of_wheel.whl
    
    73
    +```
    
    74
    +
    
    75
    +The wheel building step can be skipped with `pip`
    
    76
    +
    
    77
    +```bash
    
    78
    +pip install .
    
    79
    +```
    
    80
    +
    
    81
    +in this case the metadata files are still placed into the `dist` directory.
    
    82
    +
    
    83
    +#### Installing metadata files
    
    84
    +
    
    85
    +On the Unix platform the metadata files need to be installed also.
    
    86
    +There is a script to copy the files into the right places.
    
    87
    +See `pep517build/install_metadata.py`. You may want to edit the script and
    
    88
    +adjust the target folders depending on which distribution you use.
    
    89
    +
    
    47 90
     ### Running Tests
    
    48 91
     
    
    49
    -- `python -m unittest discover -s test` (no_gui directory)
    
    50
    -- `python -m unittest ./test/gtk/gui_file.py` (gtk directory)
    
    92
    +- `python -m unittest discover -s test`
    
    93
    +- `python -m unittest ./test/dialogs/gui_file.py` (for testing GUI files)
    
    51 94
     
    
    52
    -### Installation Procedure
    
    95
    +### Packages and install instructions
    
    53 96
     
    
    54 97
     #### Packages
    
    55 98
     
    
    ... ... @@ -68,15 +111,11 @@ #### Snapshots
    68 111
     - [Daily Linux](https://www.gajim.org/downloads/snap/)
    
    69 112
     - [Daily Windows](https://gajim.org/downloads/snap/win)
    
    70 113
     
    
    71
    -#### Linux
    
    72
    -
    
    73
    -    pip install .
    
    74
    -
    
    75 114
     #### Mac
    
    76 115
     
    
    77 116
     see [Wiki](https://dev.gajim.org/gajim/gajim/-/wikis/help/Gajim-on-macOS)
    
    78 117
     
    
    79
    -#### Developing
    
    118
    +### Developing
    
    80 119
     
    
    81 120
     To create a virtualenv you can execute
    
    82 121
     
    

  • debian/control
    ... ... @@ -11,7 +11,7 @@ Build-Depends:
    11 11
         python3-css-parser,
    
    12 12
         python3-gi,
    
    13 13
         python3-gi-cairo,
    
    14
    -    python3-nbxmpp-nightly (>=20221027),
    
    14
    +    python3-nbxmpp-nightly (>=20230123),
    
    15 15
         python3-setuptools,
    
    16 16
         python3-packaging,
    
    17 17
         python3-cryptography (>=3.4.8),
    
    ... ... @@ -35,7 +35,7 @@ Depends:
    35 35
         python3-gi (>= 3.42.0),
    
    36 36
         python3-gi-cairo (>= 1.14.0~),
    
    37 37
         python3-idna,
    
    38
    -    python3-nbxmpp-nightly (>=20221027),
    
    38
    +    python3-nbxmpp-nightly (>=20230123),
    
    39 39
         python3-cryptography (>= 3.4.8),
    
    40 40
         python3-precis-i18n,
    
    41 41
         python3-keyring,
    
    ... ... @@ -65,6 +65,7 @@ Recommends:
    65 65
         python3-sentry-sdk,
    
    66 66
     Suggests:
    
    67 67
         libxss1,
    
    68
    +Breaks: gajim
    
    68 69
     Description: Fully-featured XMPP chat client
    
    69 70
      Gajim aims to be an easy to use and fully-featured XMPP client.
    
    70 71
      Just chat with your friends or family, easily share pictures and 
    

  • gajim/common/i18n.py
    ... ... @@ -20,57 +20,18 @@
    20 20
     
    
    21 21
     from __future__ import annotations
    
    22 22
     
    
    23
    -from typing import Iterator
    
    23
    +from typing import cast
    
    24 24
     from typing import Optional
    
    25 25
     
    
    26 26
     import gettext
    
    27
    +import importlib.resources
    
    27 28
     import locale
    
    28 29
     import os
    
    29 30
     import sys
    
    30 31
     import unicodedata
    
    31 32
     from pathlib import Path
    
    32 33
     
    
    33
    -from gi.repository import GLib
    
    34
    -
    
    35 34
     DOMAIN = 'gajim'
    
    36
    -_translation = gettext.NullTranslations()
    
    37
    -
    
    38
    -
    
    39
    -def get_locale_dirs() -> Optional[list[Path]]:
    
    40
    -    if sys.platform == 'win32':
    
    41
    -        return
    
    42
    -
    
    43
    -    path = gettext.find(DOMAIN)
    
    44
    -    if path is not None:
    
    45
    -        # gettext can find the location itself
    
    46
    -        # so we don’t need the localedir
    
    47
    -        return
    
    48
    -
    
    49
    -    if Path('/app/share/run-as-flatpak').exists():
    
    50
    -        # Check if we run as flatpak
    
    51
    -        return [Path('/app/share/')]
    
    52
    -
    
    53
    -    data_dirs = [GLib.get_user_data_dir()] + GLib.get_system_data_dirs()
    
    54
    -    return [Path(dir_) for dir_ in data_dirs]
    
    55
    -
    
    56
    -
    
    57
    -def iter_locale_dirs() -> Iterator[Optional[Path]]:
    
    58
    -    locale_dirs = get_locale_dirs()
    
    59
    -    if locale_dirs is None:
    
    60
    -        yield None
    
    61
    -        return
    
    62
    -
    
    63
    -    # gettext fallback
    
    64
    -    locale_dirs.append(Path(sys.base_prefix) / 'share')
    
    65
    -
    
    66
    -    found_paths: list[Path] = []
    
    67
    -    for path in locale_dirs:
    
    68
    -        locale_dir = path / 'locale'
    
    69
    -        if locale_dir in found_paths:
    
    70
    -            continue
    
    71
    -        found_paths.append(locale_dir)
    
    72
    -        if locale_dir.is_dir():
    
    73
    -            yield locale_dir
    
    74 35
     
    
    75 36
     
    
    76 37
     def get_win32_default_lang() -> str:
    
    ... ... @@ -162,19 +123,16 @@ def ngettext(s_sing: str,
    162 123
         # doesn't retain other region settings like LC_TIME
    
    163 124
         os.environ['LANGUAGE'] = LANG
    
    164 125
     
    
    165
    -# Search for the translation in all locale dirs
    
    166
    -for dir_ in iter_locale_dirs():
    
    167
    -    try:
    
    168
    -        _translation = gettext.translation(DOMAIN, dir_)
    
    169
    -        _ = _translation.gettext
    
    170
    -        if hasattr(locale, 'bindtextdomain'):
    
    171
    -            locale.bindtextdomain(DOMAIN, dir_)
    
    172
    -    except OSError:
    
    173
    -        continue
    
    174
    -    else:
    
    175
    -        break
    
    176
    -
    
    177
    -else:
    
    126
    +
    
    127
    +package_dir = cast(Path, importlib.resources.files('gajim'))
    
    128
    +locale_dir = package_dir / 'data' / 'locale'
    
    129
    +
    
    130
    +try:
    
    131
    +    _translation = gettext.translation(DOMAIN, locale_dir)
    
    132
    +    _ = _translation.gettext
    
    133
    +    if hasattr(locale, 'bindtextdomain'):
    
    134
    +        locale.bindtextdomain(DOMAIN, locale_dir)
    
    135
    +except OSError:
    
    136
    +    _translation = gettext.NullTranslations()
    
    178 137
         _ = _translation.gettext
    
    179 138
         print('No translations found for', LANG, file=sys.stderr)
    180
    -    print('Dirs searched: %s' % get_locale_dirs(), file=sys.stderr)

  • gajim/common/jingle_rtp.py
    ... ... @@ -440,17 +440,24 @@ def setup_stream(self) -> None:
    440 440
             if 'webrtcdsp' in self.available_gst_plugins:
    
    441 441
                 self.src_bin = self.make_bin_from_config(
    
    442 442
                     'audio_input_device',
    
    443
    -                '''%s
    
    443
    +                '''
    
    444
    +                %s
    
    445
    +                ! audioconvert
    
    446
    +                ! audioresample
    
    447
    +                ! audio/x-raw,rate=48000
    
    444 448
                     ! webrtcdsp
    
    445 449
                         echo-suppression-level=high
    
    446 450
                         noise-suppression-level=very-high
    
    447 451
                         voice-detection=true
    
    448
    -                ! audioconvert ''',
    
    452
    +                ''',
    
    449 453
                     _('audio input'))
    
    450 454
             else:
    
    451 455
                 self.src_bin = self.make_bin_from_config(
    
    452 456
                     'audio_input_device',
    
    453
    -                '%s ! audioconvert ',
    
    457
    +                '''
    
    458
    +                %s
    
    459
    +                ! audioconvert
    
    460
    +                ''',
    
    454 461
                     _('audio input'))
    
    455 462
     
    
    456 463
             # setting name=webrtcechoprobe0 is needed because lingering probes
    
    ... ... @@ -458,13 +465,23 @@ def setup_stream(self) -> None:
    458 465
             if 'webrtcdsp' in self.available_gst_plugins:
    
    459 466
                 self.sink = self.make_bin_from_config(
    
    460 467
                     'audio_output_device',
    
    461
    -                '''audioconvert ! volume name=gajim_out_vol !
    
    462
    -                webrtcechoprobe name=webrtcechoprobe0 ! %s''',
    
    468
    +                '''
    
    469
    +                audioconvert
    
    470
    +                ! audioresample
    
    471
    +                ! audio/x-raw,rate=48000
    
    472
    +                ! volume name=gajim_out_vol
    
    473
    +                ! webrtcechoprobe name=webrtcechoprobe0
    
    474
    +                ! %s
    
    475
    +                ''',
    
    463 476
                     _('audio output'))
    
    464 477
             else:
    
    465 478
                 self.sink = self.make_bin_from_config(
    
    466 479
                     'audio_output_device',
    
    467
    -                'audioconvert ! volume name=gajim_out_vol ! %s',
    
    480
    +                '''
    
    481
    +                audioconvert
    
    482
    +                ! volume name=gajim_out_vol
    
    483
    +                ! %s
    
    484
    +                ''',
    
    468 485
                     _('audio output'))
    
    469 486
             self.mic_volume = self.src_bin.get_by_name('gajim_vol')
    
    470 487
             self.out_volume = self.sink.get_by_name('gajim_out_vol')
    

  • pep517build/__init__.py

  • pep517build/backend.py
    1
    +from __future__ import annotations
    
    2
    +
    
    3
    +from typing import Any
    
    4
    +
    
    5
    +import gzip
    
    6
    +import logging
    
    7
    +import shutil
    
    8
    +import subprocess
    
    9
    +import sys
    
    10
    +from pathlib import Path
    
    11
    +
    
    12
    +import setuptools.build_meta as _orig
    
    13
    +
    
    14
    +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    15
    +log = logging.getLogger()
    
    16
    +
    
    17
    +ALLOWED_CONFIG_SETTINGS = {'target'}
    
    18
    +MAN_FILES = [
    
    19
    +    Path('data/gajim.1'),
    
    20
    +    Path('data/gajim-remote.1')
    
    21
    +]
    
    22
    +META_FILES = [
    
    23
    +    (Path('data/org.gajim.Gajim.desktop.in'), '--desktop'),
    
    24
    +    (Path('data/org.gajim.Gajim.appdata.xml.in'), '--xml')
    
    25
    +]
    
    26
    +ICONS = [
    
    27
    +    Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg'),
    
    28
    +    Path('gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'),
    
    29
    +]
    
    30
    +
    
    31
    +
    
    32
    +def build_translations() -> None:
    
    33
    +    # Compile translation files and place them into "gajim/data/locale"
    
    34
    +
    
    35
    +    source_dir = Path.cwd()
    
    36
    +    translation_dir = source_dir / 'po'
    
    37
    +    locale_dir = source_dir / 'gajim' / 'data' / 'locale'
    
    38
    +
    
    39
    +    langs = sorted([lang.stem for lang in translation_dir.glob('*.po')])
    
    40
    +
    
    41
    +    for lang in langs:
    
    42
    +        po_file = source_dir / 'po' / f'{lang}.po'
    
    43
    +        mo_file = locale_dir / lang / 'LC_MESSAGES' / 'gajim.mo'
    
    44
    +        mo_file.parent.mkdir(parents=True, exist_ok=True)
    
    45
    +
    
    46
    +        log.info('Compile %s >> %s', po_file, mo_file)
    
    47
    +
    
    48
    +        subprocess.run(['msgfmt',
    
    49
    +                        str(po_file),
    
    50
    +                        '-o',
    
    51
    +                        str(mo_file)],
    
    52
    +                       check=True)
    
    53
    +
    
    54
    +
    
    55
    +def build_man(target_path: Path) -> None:
    
    56
    +    # Build man files in target path
    
    57
    +
    
    58
    +    for man_path in MAN_FILES:
    
    59
    +        data = man_path.read_bytes()
    
    60
    +        man_file_name = man_path.name
    
    61
    +
    
    62
    +        man_out_path = target_path / f'{man_file_name}.gz'
    
    63
    +        log.info('Compress %s >> %s', man_file_name, man_out_path)
    
    64
    +
    
    65
    +        with gzip.open(man_out_path, 'wb') as f_out:
    
    66
    +            f_out.write(data)
    
    67
    +
    
    68
    +
    
    69
    +
    
    70
    +def build_intl(target_path: Path) -> None:
    
    71
    +    # Merge translation files into desktop and metadata files
    
    72
    +
    
    73
    +    for file_path, option in META_FILES:
    
    74
    +        out_path = target_path / file_path.name
    
    75
    +        out_path = out_path.with_suffix('')
    
    76
    +
    
    77
    +        log.info('Compile %s >> %s', file_path, out_path)
    
    78
    +
    
    79
    +        subprocess.run(['msgfmt',
    
    80
    +                        option,
    
    81
    +                        '-d',
    
    82
    +                        'po',
    
    83
    +                        '--template',
    
    84
    +                        str(file_path),
    
    85
    +                        '-o',
    
    86
    +                        str(out_path)],
    
    87
    +                        check=True)
    
    88
    +
    
    89
    +
    
    90
    +def build_app_icons(target_path: Path) -> None:
    
    91
    +    for file_path in ICONS:
    
    92
    +        out_path = target_path / file_path.name
    
    93
    +
    
    94
    +        log.info('Copy %s >> %s', file_path, out_path)
    
    95
    +        shutil.copy2(file_path, out_path)
    
    96
    +
    
    97
    +
    
    98
    +def _check_config_settings(config_settings: dict[str, str]) -> None:
    
    99
    +    settings = set(config_settings.keys()) - ALLOWED_CONFIG_SETTINGS
    
    100
    +    if settings:
    
    101
    +        raise ValueError('Unknown config setting %s' % settings)
    
    102
    +
    
    103
    +
    
    104
    +def _get_target_path(wheel_directory: str,
    
    105
    +                     config_settings: dict[str, str] | None) -> Path:
    
    106
    +
    
    107
    +    path = Path(wheel_directory)
    
    108
    +    if config_settings is None:
    
    109
    +        path = path / 'metadata'
    
    110
    +    else:
    
    111
    +        path = path / config_settings.get('target', 'metadata')
    
    112
    +
    
    113
    +    path.mkdir(parents=True, exist_ok=True)
    
    114
    +    return path
    
    115
    +
    
    116
    +
    
    117
    +def get_requires_for_build_sdist(*args: Any, **kwargs: Any) -> str:
    
    118
    +    return _orig.get_requires_for_build_sdist(*args, **kwargs)
    
    119
    +
    
    120
    +def build_sdist(*args: Any, **kwargs: Any) -> str:
    
    121
    +    return _orig.build_sdist(*args, **kwargs)
    
    122
    +
    
    123
    +def get_requires_for_build_wheel(*args: Any, **kwargs: Any) -> str:
    
    124
    +    return _orig.get_requires_for_build_wheel(*args, **kwargs)
    
    125
    +
    
    126
    +def prepare_metadata_for_build_wheel(*args: Any, **kwargs: Any) -> str:
    
    127
    +    return _orig.prepare_metadata_for_build_wheel(*args, **kwargs)
    
    128
    +
    
    129
    +def build_wheel(wheel_directory: str,
    
    130
    +                config_settings: dict[str, str] | None = None,
    
    131
    +                metadata_directory: str | None = None
    
    132
    +                ) -> str:
    
    133
    +
    
    134
    +    if config_settings is not None:
    
    135
    +        _check_config_settings(config_settings)
    
    136
    +    target_path = _get_target_path(wheel_directory, config_settings)
    
    137
    +
    
    138
    +    build_translations()
    
    139
    +
    
    140
    +    basename = _orig.build_wheel(
    
    141
    +        wheel_directory,
    
    142
    +        config_settings=config_settings,
    
    143
    +        metadata_directory=metadata_directory,
    
    144
    +    )
    
    145
    +
    
    146
    +    if sys.platform not in ('win32', 'darwin'):
    
    147
    +        build_man(target_path)
    
    148
    +        build_intl(target_path)
    
    149
    +        build_app_icons(target_path)
    
    150
    +
    
    151
    +    return basename

  • pep517build/install_metadata.py
    1
    +#!/usr/bin/env python
    
    2
    +
    
    3
    +from __future__ import annotations
    
    4
    +
    
    5
    +import argparse
    
    6
    +import logging
    
    7
    +import shutil
    
    8
    +from pathlib import Path
    
    9
    +
    
    10
    +logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    11
    +log = logging.getLogger()
    
    12
    +
    
    13
    +DEFAULT_METADATA_PATH = Path('dist/metadata')
    
    14
    +
    
    15
    +FILES = {
    
    16
    +    'gajim-remote.1.gz': 'share/man/man1',
    
    17
    +    'gajim.1.gz': 'share/man/man1',
    
    18
    +    'org.gajim.Gajim.desktop': 'share/applications',
    
    19
    +    'org.gajim.Gajim-symbolic.svg': 'share/icons/hicolor/scalable/apps',
    
    20
    +    'org.gajim.Gajim.svg': 'share/icons/hicolor/scalable/apps',
    
    21
    +    'org.gajim.Gajim.appdata.xml': 'share/metainfo',
    
    22
    +}
    
    23
    +
    
    24
    +if __name__ == '__main__':
    
    25
    +    parser = argparse.ArgumentParser(description='Install metadata files')
    
    26
    +    parser.add_argument('prefix', type=Path)
    
    27
    +    parser.add_argument('--path', type=Path, default=DEFAULT_METADATA_PATH)
    
    28
    +    args = parser.parse_args()
    
    29
    +
    
    30
    +    for file, path in FILES.items():
    
    31
    +        src = args.path / file
    
    32
    +        dest_dir = args.prefix / path
    
    33
    +        log.info('Copy %s to %s', src, dest_dir)
    
    34
    +        if not dest_dir.exists():
    
    35
    +            dest_dir.mkdir(parents=True)
    
    36
    +        shutil.copy(src, dest_dir / file)

  • pyproject.toml
    1 1
     [build-system]
    
    2 2
     requires = [
    
    3
    -  "setuptools >= 43.0.0",
    
    3
    +  "setuptools >= 65.0.0",
    
    4 4
     ]
    
    5
    -build-backend = "setuptools.build_meta"
    
    5
    +build-backend = "backend"
    
    6
    +backend-path = ["pep517build"]
    
    7
    +
    
    8
    +[project]
    
    9
    +name = "gajim"
    
    10
    +description = "A GTK XMPP Client"
    
    11
    +readme = "README.md"
    
    12
    +requires-python = ">=3.10"
    
    13
    +license = {text = "GPL-3.0-or-later"}
    
    14
    +authors = [
    
    15
    +    {name = "Philipp Hörist", email = "philipp@hoerist.com"},
    
    16
    +    {name = "Yann Leboulanger", email = "yann@leboulanger.org"},
    
    17
    +]
    
    18
    +keywords = ["chat", "messaging", "im", "xmpp", "voip"]
    
    19
    +classifiers = [
    
    20
    +  "Development Status :: 5 - Production/Stable",
    
    21
    +  "Environment :: X11 Applications :: GTK",
    
    22
    +  "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
    
    23
    +  "Operating System :: OS Independent",
    
    24
    +  "Programming Language :: Python :: 3.10",
    
    25
    +  "Topic :: Communications :: Chat",
    
    26
    +]
    
    27
    +dependencies = [
    
    28
    +  "css-parser",
    
    29
    +  "keyring",
    
    30
    +  "nbxmpp>=3.2.5,<4.0.0",
    
    31
    +  "packaging",
    
    32
    +  "pillow",
    
    33
    +  "precis-i18n>=1.0.0",
    
    34
    +  "cryptography>=3.4.8",
    
    35
    +  "pycairo>=1.16.0",
    
    36
    +  "PyGObject>=3.42.0",
    
    37
    +]
    
    38
    +dynamic = ["version"]
    
    39
    +
    
    40
    +[project.urls]
    
    41
    +homepage = "https://gajim.org"
    
    42
    +repository = "https://dev.gajim.org/gajim/gajim"
    
    43
    +
    
    44
    +[project.optional-dependencies]
    
    45
    +dev = [
    
    46
    +  "ruff",
    
    47
    +  "codespell[toml]",
    
    48
    +  "nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git",
    
    49
    +  "PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git",
    
    50
    +  "python-gitlab",
    
    51
    +  "sentry-sdk",
    
    52
    +]
    
    53
    +
    
    54
    +[project.scripts]
    
    55
    +gajim-remote = "gajim.gajim_remote:main"
    
    56
    +
    
    57
    +[project.gui-scripts]
    
    58
    +gajim = "gajim.gajim:main"
    
    6 59
     
    
    60
    +[tool.setuptools.packages.find]
    
    61
    +include = ["gajim*"]
    
    62
    +
    
    63
    +[tool.setuptools.package-data]
    
    64
    +gajim = ["py.typed"]
    
    65
    +"gajim.data" = ["**/*"]
    
    66
    +
    
    67
    +[tool.setuptools.dynamic]
    
    68
    +version = {attr = "gajim.__version__"}
    
    7 69
     
    
    8 70
     [tool.codespell]
    
    9
    -skip = "*__pycache__*,build,test,*.egg-info,.git,*.po,*.nsi,*.spec"
    
    71
    +skip = "*__pycache__*,build,dist,test,*.egg-info,.git,*.po,*.nsi,*.spec"
    
    10 72
     ignore-words-list = "claus,pres,ser,trough"
    
    11 73
     
    
    12
    -
    
    13 74
     [tool.pyright]
    
    14 75
     pythonVersion = "3.10"
    
    15 76
     pythonPlatform = "All"
    
    ... ... @@ -46,6 +107,7 @@ include = [
    46 107
         "mac/*",
    
    47 108
         "scripts/*",
    
    48 109
         "win/*",
    
    110
    +    "pep517build/*",
    
    49 111
         "gajim/common/application.py",
    
    50 112
         "gajim/common/call_manager.py",
    
    51 113
         "gajim/common/cert_store.py",
    
    ... ... @@ -80,7 +142,6 @@ include = [
    80 142
         "gajim/plugins/*",
    
    81 143
     ]
    
    82 144
     
    
    83
    -
    
    84 145
     [tool.ruff]
    
    85 146
     line-length = 80
    
    86 147
     
    
    ... ... @@ -164,7 +225,6 @@ target-version = "py310"
    164 225
     "test/common/test_styling.py" = ["RUF001", "E501"]
    
    165 226
     "test/common/test_regex.py" = ["RUF001"]
    
    166 227
     
    
    167
    -
    
    168 228
     [tool.ruff.mccabe]
    
    169 229
     max-complexity = 15
    
    170 230
     
    

  • setup.cfg deleted
    1
    -[metadata]
    
    2
    -name = gajim
    
    3
    -version = attr: gajim.__version__
    
    4
    -description = A GTK XMPP client
    
    5
    -long_description = file: README.md
    
    6
    -long_description_content_type = text/markdown
    
    7
    -author = Philipp Hoerist, Yann Leboulanger
    
    8
    -author_email = gajim-devel@gajim.org
    
    9
    -url = https://gajim.org
    
    10
    -license = GPL v3
    
    11
    -keywords = chat,messaging,im,xmpp,voip
    
    12
    -classifiers =
    
    13
    -    Development Status :: 5 - Production/Stable
    
    14
    -    Environment :: X11 Applications :: GTK
    
    15
    -    License :: OSI Approved :: GNU General Public License v3 (GPLv3)
    
    16
    -    Operating System :: OS Independent
    
    17
    -    Programming Language :: Python :: 3.10
    
    18
    -    Topic :: Communications :: Chat
    
    19
    -
    
    20
    -[options]
    
    21
    -python_requires = >=3.10
    
    22
    -packages = find:
    
    23
    -test_suite = test
    
    24
    -install_requires =
    
    25
    -    css-parser
    
    26
    -    keyring
    
    27
    -    nbxmpp>=4.0.1,<5.0.0
    
    28
    -    packaging
    
    29
    -    pillow
    
    30
    -    precis-i18n>=1.0.0
    
    31
    -    cryptography>=3.4.8
    
    32
    -    pycairo>=1.16.0
    
    33
    -    PyGObject>=3.42.0
    
    34
    -
    
    35
    -[options.extras_require]
    
    36
    -dev =
    
    37
    -    codespell[toml]
    
    38
    -    isort
    
    39
    -    nbxmpp @ git+https://dev.gajim.org/gajim/python-nbxmpp.git
    
    40
    -    pre-commit
    
    41
    -    PyGObject-stubs @ git+https://github.com/pygobject/pygobject-stubs.git
    
    42
    -    python-gitlab
    
    43
    -    ruamel.yaml
    
    44
    -    ruff
    
    45
    -    sentry-sdk
    
    46
    -
    
    47
    -[options.package_data]
    
    48
    -gajim =
    
    49
    -    data/gui/*.ui
    
    50
    -    data/icons/hicolor/*/*/*.png
    
    51
    -    data/icons/hicolor/*/*/*.svg
    
    52
    -    data/other/*
    
    53
    -    data/sounds/*.wav
    
    54
    -    data/style/*.css
    
    55
    -    data/plugins/*/*
    
    56
    -    py.typed
    
    57
    -
    
    58
    -[options.packages.find]
    
    59
    -exclude =
    
    60
    -    gajim.dev
    
    61
    -    test*

  • setup.py deleted
    1
    -#!/usr/bin/env python3
    
    2
    -
    
    3
    -from __future__ import annotations
    
    4
    -
    
    5
    -from typing import cast
    
    6
    -
    
    7
    -import logging
    
    8
    -import os
    
    9
    -import sys
    
    10
    -
    
    11
    -if sys.version_info < (3, 10):
    
    12
    -    sys.exit('Gajim needs Python 3.10+')
    
    13
    -
    
    14
    -import subprocess
    
    15
    -from pathlib import Path
    
    16
    -
    
    17
    -from setuptools import setup
    
    18
    -from setuptools.command.build_py import build_py as _build
    
    19
    -from setuptools.command.install import install as _install
    
    20
    -
    
    21
    -DataFilesT = list[tuple[str, list[str]]]
    
    22
    -
    
    23
    -
    
    24
    -MAN_FILES = [
    
    25
    -    'gajim.1',
    
    26
    -    'gajim-remote.1'
    
    27
    -]
    
    28
    -META_FILES = [
    
    29
    -    ('data/org.gajim.Gajim.desktop', 'share/applications', '--desktop'),
    
    30
    -    ('data/org.gajim.Gajim.appdata.xml', 'share/metainfo', '--xml')]
    
    31
    -
    
    32
    -
    
    33
    -TRANS_DIR = Path('po')
    
    34
    -TRANS_TEMPLATE = TRANS_DIR / 'gajim.pot'
    
    35
    -REPO_DIR = Path(__file__).resolve().parent
    
    36
    -BUILD_DIR = REPO_DIR / 'build'
    
    37
    -
    
    38
    -ALL_LINGUAS = sorted([lang.stem for lang in TRANS_DIR.glob('*.po')])
    
    39
    -
    
    40
    -
    
    41
    -logging.basicConfig(level='INFO', format='%(levelname)s: %(message)s')
    
    42
    -log = logging.getLogger()
    
    43
    -
    
    44
    -
    
    45
    -def newer(source: Path, target: Path) -> bool:
    
    46
    -    if not source.exists():
    
    47
    -        raise ValueError('file "%s" does not exist' % source.resolve())
    
    48
    -    if not target.exists():
    
    49
    -        return True
    
    50
    -
    
    51
    -    from stat import ST_MTIME
    
    52
    -    mtime1 = source.stat()[ST_MTIME]
    
    53
    -    mtime2 = target.stat()[ST_MTIME]
    
    54
    -
    
    55
    -    return mtime1 > mtime2
    
    56
    -
    
    57
    -
    
    58
    -def build_translation() -> None:
    
    59
    -    for lang in ALL_LINGUAS:
    
    60
    -        po_file = TRANS_DIR / f'{lang}.po'
    
    61
    -        mo_file = BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo'
    
    62
    -        mo_dir = mo_file.parent
    
    63
    -        if not (mo_dir.is_dir() or mo_dir.is_symlink()):
    
    64
    -            mo_dir.mkdir(parents=True)
    
    65
    -
    
    66
    -        if newer(po_file, mo_file):
    
    67
    -            subprocess.run(['msgfmt',
    
    68
    -                            str(po_file),
    
    69
    -                            '-o',
    
    70
    -                            str(mo_file)],
    
    71
    -                           cwd=REPO_DIR,
    
    72
    -                           check=True)
    
    73
    -
    
    74
    -            log.info('Compiling %s >> %s', po_file, mo_file)
    
    75
    -
    
    76
    -
    
    77
    -def install_trans(data_files: DataFilesT) -> None:
    
    78
    -    for lang in ALL_LINGUAS:
    
    79
    -        mo_file = str(BUILD_DIR / 'mo' / lang / 'LC_MESSAGES' / 'gajim.mo')
    
    80
    -        target = f'share/locale/{lang}/LC_MESSAGES'
    
    81
    -        data_files.append((target, [mo_file]))
    
    82
    -
    
    83
    -
    
    84
    -def build_man() -> None:
    
    85
    -    '''
    
    86
    -    Compress Gajim manual files
    
    87
    -    '''
    
    88
    -    newdir = BUILD_DIR / 'man'
    
    89
    -    if not (newdir.is_dir() or newdir.is_symlink()):
    
    90
    -        newdir.mkdir()
    
    91
    -
    
    92
    -    for man in MAN_FILES:
    
    93
    -        filename = Path('data') / man
    
    94
    -        man_file_gz = newdir / (man + '.gz')
    
    95
    -        if man_file_gz.exists():
    
    96
    -            if newer(filename, man_file_gz):
    
    97
    -                man_file_gz.unlink()
    
    98
    -            else:
    
    99
    -                continue
    
    100
    -
    
    101
    -        import gzip
    
    102
    -
    
    103
    -        # Binary io, so open is OK
    
    104
    -        with open(filename, 'rb') as f_in,\
    
    105
    -                gzip.open(man_file_gz, 'wb') as f_out:
    
    106
    -            f_out.writelines(f_in)
    
    107
    -            log.info('Compiling %s >> %s', filename, man_file_gz)
    
    108
    -
    
    109
    -
    
    110
    -def install_man(data_files: DataFilesT) -> None:
    
    111
    -    man_dir = BUILD_DIR / 'man'
    
    112
    -    target = 'share/man/man1'
    
    113
    -
    
    114
    -    for man in MAN_FILES:
    
    115
    -        man_file_gz = str(man_dir / (man + '.gz'))
    
    116
    -        data_files.append((target, [man_file_gz]))
    
    117
    -
    
    118
    -
    
    119
    -def build_intl() -> None:
    
    120
    -    '''
    
    121
    -    Merge translation files into desktop and mime files
    
    122
    -    '''
    
    123
    -    base = BUILD_DIR
    
    124
    -
    
    125
    -    for filename, _, option in META_FILES:
    
    126
    -        newfile = base / filename
    
    127
    -        newdir = newfile.parent
    
    128
    -        if not (newdir.is_dir() or newdir.is_symlink()):
    
    129
    -            newdir.mkdir()
    
    130
    -        merge(Path(filename + '.in'), newfile, option)
    
    131
    -
    
    132
    -
    
    133
    -def install_intl(data_files: DataFilesT) -> None:
    
    134
    -    for filename, target, _ in META_FILES:
    
    135
    -        data_files.append((target, [str(BUILD_DIR / filename)]))
    
    136
    -
    
    137
    -
    
    138
    -def merge(in_file: Path,
    
    139
    -          out_file: Path,
    
    140
    -          option: str,
    
    141
    -          po_dir: str = 'po') -> None:
    
    142
    -    '''
    
    143
    -    Run the msgfmt command.
    
    144
    -    '''
    
    145
    -    if in_file.exists():
    
    146
    -        cmd = (('msgfmt %(opt)s -d %(po_dir)s --template %(in_file)s '
    
    147
    -                '-o %(out_file)s') %
    
    148
    -               {'opt': option,
    
    149
    -                'po_dir': po_dir,
    
    150
    -                'in_file': in_file,
    
    151
    -                'out_file': out_file})
    
    152
    -        if os.system(cmd) != 0:
    
    153
    -            msg = ('ERROR: %s was not merged into the translation files!\n' %
    
    154
    -                   out_file)
    
    155
    -            raise SystemExit(msg)
    
    156
    -        log.info('Compiling %s >> %s', in_file, out_file)
    
    157
    -
    
    158
    -
    
    159
    -class Build(_build):
    
    160
    -    def run(self):
    
    161
    -        build_translation()
    
    162
    -        if sys.platform != 'win32':
    
    163
    -            build_man()
    
    164
    -            build_intl()
    
    165
    -        _build.run(self)
    
    166
    -
    
    167
    -
    
    168
    -class Install(_install):
    
    169
    -    def run(self):
    
    170
    -        data_files = cast(DataFilesT, self.distribution.data_files)  # pyright: ignore  # noqa: E501
    
    171
    -        install_trans(data_files)
    
    172
    -        if sys.platform != 'win32':
    
    173
    -            install_man(data_files)
    
    174
    -            install_intl(data_files)
    
    175
    -        _install.run(self)  # pyright: ignore
    
    176
    -
    
    177
    -
    
    178
    -# only install subdirectories of data
    
    179
    -data_files_app_icon = [
    
    180
    -    ('share/icons/hicolor/scalable/apps',
    
    181
    -     ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim.svg']),
    
    182
    -    ('share/icons/hicolor/scalable/apps',
    
    183
    -     ['gajim/data/icons/hicolor/scalable/apps/org.gajim.Gajim-symbolic.svg'])
    
    184
    -]
    
    185
    -
    
    186
    -data_files: DataFilesT = data_files_app_icon
    
    187
    -
    
    188
    -setup(
    
    189
    -    cmdclass={
    
    190
    -        'build_py': Build,
    
    191
    -        'install': Install,
    
    192
    -    },
    
    193
    -    entry_points={
    
    194
    -        'console_scripts': [
    
    195
    -            'gajim-remote = gajim.gajim_remote:main',
    
    196
    -        ],
    
    197
    -        'gui_scripts': [
    
    198
    -            'gajim = gajim.gajim:main',
    
    199
    -        ]
    
    200
    -    },
    
    201
    -    data_files=data_files
    
    202
    -)