Philipp Hörist pushed to branch master at gajim / gajim

Commits:

5 changed files:

Changes:

  • .gitlab-ci.yml
    1 1
     image: ci-gajim:master
    
    2 2
     
    
    3
    -include:
    
    4
    -- project: 'gajim/ci'
    
    5
    -  ref: master
    
    6
    -  file: '.gajim-ci.yml'
    
    3
    +workflow:
    
    4
    +  rules:
    
    5
    +    - if: $GAJIM_NIGHTLY_BUILD
    
    6
    +    - if: $CI_PIPELINE_SOURCE == "push"
    
    7
    +
    
    8
    +stages:
    
    9
    +  - test
    
    10
    +  - build
    
    11
    +  - deploy
    
    7 12
     
    
    8 13
     pyright:
    
    9 14
       stage: test
    
    10 15
       script:
    
    11
    -    - pip3 install git+https://dev.gajim.org/gajim/python-nbxmpp.git@$NBXMPP_BRANCH
    
    16
    +    - pip3 install git+https://dev.gajim.org/gajim/python-nbxmpp.git
    
    12 17
         - pip3 install git+https://github.com/pygobject/pygobject-stubs.git
    
    13 18
         - npm install pyright
    
    14 19
         - python3 -V
    
    ... ... @@ -16,8 +21,77 @@ pyright:
    16 21
         - $(npm bin)/pyright --version
    
    17 22
         - $(npm bin)/pyright
    
    18 23
       interruptible: true
    
    19
    -  allow_failure: false
    
    20 24
     
    
    21
    -variables:
    
    22
    -  NBXMPP_BRANCH: master
    
    23
    -  PLUGINS_BRANCH: master
    25
    +code-quality:
    
    26
    +  stage: test
    
    27
    +  script:
    
    28
    +    - python3 -V
    
    29
    +    - pip3 install -I git+https://dev.gajim.org/gajim/python-nbxmpp.git
    
    30
    +    - scripts/ci/pylint.sh --jobs=2 gajim
    
    31
    +    - coverage run --source=gajim -m unittest discover -s test -v
    
    32
    +    - coverage report -mi
    
    33
    +    - coverage xml -i
    
    34
    +    - codespell -I codespell.conf --skip="*__pycache__*,gajim/data/icons,gajim/data/sounds,gajim/data/emoticons" gajim
    
    35
    +    - python3 setup.py build
    
    36
    +    - appstream-util validate build/data/org.gajim.Gajim.appdata.xml
    
    37
    +  coverage: "/TOTAL.+ ([0-9]{1,3}%)/"
    
    38
    +  artifacts:
    
    39
    +    reports:
    
    40
    +      cobertura: coverage.xml
    
    41
    +  interruptible: true
    
    42
    +
    
    43
    +build-linux:
    
    44
    +  stage: build
    
    45
    +  rules:
    
    46
    +    - if: '$GAJIM_NIGHTLY_BUILD'
    
    47
    +    - if: '$CI_COMMIT_TAG'
    
    48
    +      when: manual
    
    49
    +  script:
    
    50
    +    - python3 ./scripts/ci/sdist.py
    
    51
    +
    
    52
    +  artifacts:
    
    53
    +    name: "gajim-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHA"
    
    54
    +    expire_in: 1 week
    
    55
    +    paths:
    
    56
    +      - dist/gajim-*.tar.gz
    
    57
    +
    
    58
    +build-windows:
    
    59
    +  stage: build
    
    60
    +  rules:
    
    61
    +    - if: '$GAJIM_NIGHTLY_BUILD'
    
    62
    +    - if: '$CI_COMMIT_TAG'
    
    63
    +  script:
    
    64
    +    - python3 ./win/ci/appveyor_build.py
    
    65
    +
    
    66
    +  artifacts:
    
    67
    +    expire_in: 1 day
    
    68
    +    paths:
    
    69
    +      - build/Gajim-*.exe
    
    70
    +
    
    71
    +deploy-windows:
    
    72
    +  stage: deploy
    
    73
    +  needs: ['build-windows']
    
    74
    +  rules:
    
    75
    +    - if: '$GAJIM_NIGHTLY_BUILD'
    
    76
    +    - if: '$CI_COMMIT_TAG'
    
    77
    +      when: manual
    
    78
    +  variables:
    
    79
    +    DEPLOY_TYPE: "windows"
    
    80
    +  script:
    
    81
    +    - python3 ./scripts/ci/deploy.py build
    
    82
    +
    
    83
    +deploy-flatpak:
    
    84
    +  stage: deploy
    
    85
    +  needs: []
    
    86
    +  rules:
    
    87
    +    - if: '$CI_COMMIT_TAG'
    
    88
    +  when: manual
    
    89
    +  script:
    
    90
    +    - git clone https://github.com/flathub/org.gajim.Gajim.git
    
    91
    +    - cd org.gajim.Gajim
    
    92
    +    - git config user.email "ci@gajim.org"
    
    93
    +    - git config user.name "CI Deploy"
    
    94
    +    - mv ../flatpak/org.gajim.Gajim.yaml org.gajim.Gajim.yaml
    
    95
    +    - git diff
    
    96
    +    - git add org.gajim.Gajim.yaml
    
    97
    +    - git commit -m "$CI_COMMIT_TAG"

  • scripts/ci/deploy.py
    1
    +import os
    
    2
    +import sys
    
    3
    +from ftplib import FTP_TLS, ftpcp
    
    4
    +from pathlib import Path
    
    5
    +import functools
    
    6
    +from typing import Any
    
    7
    +
    
    8
    +from rich.console import Console
    
    9
    +
    
    10
    +FTP_URL = 'panoramix.gajim.org'
    
    11
    +FTP_USER = os.environ['FTP_USER']
    
    12
    +FTP_PASS = os.environ['FTP_PASS']
    
    13
    +
    
    14
    +WINDOWS_NIGHTLY_FOLDER = 'win_snap'
    
    15
    +
    
    16
    +console = Console()
    
    17
    +
    
    18
    +
    
    19
    +def ftp_connection(func: Any) -> Any:
    
    20
    +    @functools.wraps(func)
    
    21
    +    def func_wrapper(filedir: Path) -> None:
    
    22
    +        ftp = FTP_TLS(FTP_URL, FTP_USER, FTP_PASS)
    
    23
    +        console.print('Successfully connected to', FTP_URL)
    
    24
    +        func(ftp, filedir)
    
    25
    +        ftp.quit()
    
    26
    +        console.print('Quit')
    
    27
    +        return
    
    28
    +    return func_wrapper
    
    29
    +
    
    30
    +
    
    31
    +def get_release_folder_from_tag(tag: str) -> str:
    
    32
    +    numbers = tag.split('.')
    
    33
    +    return '.'.join(numbers[:2])
    
    34
    +
    
    35
    +
    
    36
    +def get_gajim_tag() -> str:
    
    37
    +    tag = os.environ.get('CI_COMMIT_TAG')
    
    38
    +    if tag is None:
    
    39
    +        exit('No tag found')
    
    40
    +    return tag.removesuffix('gajim-')
    
    41
    +
    
    42
    +
    
    43
    +def get_dir_list(ftp: FTP_TLS) -> list[str]:
    
    44
    +    return [x[0] for x in ftp.mlsd()]
    
    45
    +
    
    46
    +
    
    47
    +def ensure_folder_exists(ftp: FTP_TLS, dirname: str) -> None:
    
    48
    +    dir_list = get_dir_list(ftp)
    
    49
    +    if dirname not in dir_list:
    
    50
    +        ftp.mkd(dirname)
    
    51
    +
    
    52
    +
    
    53
    +def upload_all_from_dir(ftp: FTP_TLS, dir: Path) -> None:
    
    54
    +    for file_path in dir.iterdir():
    
    55
    +        console.print('Upload file', file_path.name)
    
    56
    +        with open(file_path, 'rb') as f:
    
    57
    +            ftp.storbinary('STOR ' + file_path.name, f)
    
    58
    +
    
    59
    +
    
    60
    +def get_deploy_method() -> str:
    
    61
    +    deploy_type = os.environ['DEPLOY_TYPE']
    
    62
    +    is_nightly = bool(os.environ.get('GAJIM_NIGHTLY_BUILD'))
    
    63
    +    if is_nightly:
    
    64
    +        return f'deploy_{deploy_type}_nightly'
    
    65
    +    return f'deploy_{deploy_type}_release'
    
    66
    +
    
    67
    +
    
    68
    +@ftp_connection
    
    69
    +def deploy_windows_nightly(ftp: FTP_TLS, filedir: Path) -> None:
    
    70
    +    ftp.cwd(WINDOWS_NIGHTLY_FOLDER)
    
    71
    +    upload_all_from_dir(ftp, filedir)
    
    72
    +
    
    73
    +
    
    74
    +@ftp_connection
    
    75
    +def deploy_windows_release(ftp: FTP_TLS, filedir: Path) -> None:
    
    76
    +    tag = get_gajim_tag()
    
    77
    +    folder = get_release_folder_from_tag(tag)
    
    78
    +    ensure_folder_exists(ftp, folder)
    
    79
    +    ftp.cwd(folder)
    
    80
    +    upload_all_from_dir(ftp, filedir)
    
    81
    +
    
    82
    +
    
    83
    +@ftp_connection
    
    84
    +def deploy_linux_nightly():
    
    85
    +    raise NotImplementedError
    
    86
    +
    
    87
    +
    
    88
    +@ftp_connection
    
    89
    +def deploy_linux_release():
    
    90
    +    raise NotImplementedError
    
    91
    +
    
    92
    +
    
    93
    +if __name__ == '__main__':
    
    94
    +    filedir = Path(sys.argv[1])
    
    95
    +    current_module = sys.modules[__name__]
    
    96
    +    method = getattr(current_module, get_deploy_method())
    
    97
    +    method(filedir)

  • scripts/ci/sdist.py
    1
    +#!/usr/bin/env python3
    
    2
    +
    
    3
    +import io
    
    4
    +import zipfile
    
    5
    +import subprocess
    
    6
    +import shutil
    
    7
    +from pathlib import Path
    
    8
    +import requests
    
    9
    +
    
    10
    +PLUGINS = [
    
    11
    +    'plugin_installer',
    
    12
    +]
    
    13
    +
    
    14
    +PLUGINS_BASE_URL = 'https://ftp.gajim.org'
    
    15
    +PLUGINS_FOLDER = Path('./gajim/data/plugins')
    
    16
    +
    
    17
    +
    
    18
    +def get_plugins_url(plugin):
    
    19
    +    return f'{PLUGINS_BASE_URL}/plugins_master_zip/{plugin}.zip'
    
    20
    +
    
    21
    +
    
    22
    +def extraxt_zip(zip_bytes, path):
    
    23
    +    print('Extract to', path)
    
    24
    +    with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zip_file:
    
    25
    +        zip_file.extractall(path)
    
    26
    +
    
    27
    +
    
    28
    +def download_plugins():
    
    29
    +    PLUGINS_FOLDER.mkdir(parents=True)
    
    30
    +    for plugin in PLUGINS:
    
    31
    +        url = get_plugins_url(plugin)
    
    32
    +        print('Download', url)
    
    33
    +        req = requests.get(url)
    
    34
    +        req.raise_for_status()
    
    35
    +        extraxt_zip(req.content, PLUGINS_FOLDER)
    
    36
    +
    
    37
    +
    
    38
    +def setup():
    
    39
    +    print('Setup')
    
    40
    +    subprocess.call(['python3', 'setup.py', 'sdist'])
    
    41
    +
    
    42
    +
    
    43
    +def cleanup():
    
    44
    +    print('Cleanup')
    
    45
    +    shutil.rmtree(PLUGINS_FOLDER)
    
    46
    +
    
    47
    +
    
    48
    +download_plugins()
    
    49
    +setup()
    
    50
    +cleanup()

  • appveyor.yml → win/ci/appveyor.yml
    1
    -image: Visual Studio 2019
    
    2
    -
    
    3
    -environment:
    
    4
    -  matrix:
    
    5
    -    - MSYSTEM: MINGW64
    
    6
    -      MSYS_ARCH: "x86_64"
    
    7
    -      ARCH: "64bit"
    
    8
    -
    
    9
    -    - MSYSTEM: MINGW32
    
    10
    -      MSYS_ARCH: "i686"
    
    11
    -      ARCH: "32bit"
    
    12
    -
    
    13
    -branches:
    
    14
    -  only:
    
    15
    -    - master
    
    16
    -
    
    17
    -clone_depth: 1
    
    18
    -
    
    19
    -# init:
    
    20
    -#   - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
    
    21
    -
    
    22
    -install:
    
    23
    -  - set PATH=C:\msys64\usr\bin;%PATH%
    
    24
    -  - bash -lc "pacman --needed --noconfirm -Syu"
    
    25
    -  # This is needed because without killing all processes -Su will fail
    
    26
    -  - ps: Get-Process | Where-Object {$_.path -like 'C:\msys64*'} | Stop-Process
    
    27
    -  - bash -lc "pacman -Sydd --noconfirm filesystem"
    
    28
    -  - bash -lc "pacman --needed --noconfirm -Su"
    
    29
    -
    
    30
    -build_script:
    
    31
    -
    
    32
    -  - ps: |
    
    33
    -        $env:TIME_STRING=(get-date -UFormat "%Y-%m-%d").ToString()
    
    34
    -        $env:BUILDROOT="C:\msys64\home\appveyor\gajim\win\_build_root"
    
    35
    -
    
    36
    -        function bash($command) {
    
    37
    -          Write-Host $command -NoNewline
    
    38
    -          C:\msys64\usr\bin\sh.exe --login -c $command
    
    39
    -        }
    
    40
    -
    
    41
    -        bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
    
    42
    -        bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)"
    
    43
    -        Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-Master-$($env:ARCH)-$($env:TIME_STRING).exe"
    
    44
    -        Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-Master-$($env:ARCH)-$($env:TIME_STRING).exe"
    
    45
    -
    
    46
    -# on_finish:
    
    47
    -#   - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
    
    48
    - 
    
    49
    -deploy:
    
    50
    -    # FTP deployment provider settings
    
    51
    -  - provider: FTP
    
    52
    -    protocol: ftp
    
    53
    -    host: panoramix.gajim.org
    
    54
    -    username:
    
    55
    -      secure: SNcOJDhUtBjfddbKXudE2w==
    
    56
    -    password:
    
    57
    -      secure: tQkYbcUb6nChCp0cdqo4CA==
    
    58
    -    folder: win_snap
    
    59
    -    debug: true
    1
    +version: 1.1.{build}
    
    2
    +
    
    3
    +image: Visual Studio 2019
    
    4
    +
    
    5
    +environment:
    
    6
    +  matrix:
    
    7
    +    - MSYSTEM: MINGW64
    
    8
    +      MSYS_ARCH: "x86_64"
    
    9
    +      ARCH: "64bit"
    
    10
    +
    
    11
    +    - MSYSTEM: MINGW32
    
    12
    +      MSYS_ARCH: "i686"
    
    13
    +      ARCH: "32bit"
    
    14
    +
    
    15
    +branches:
    
    16
    +  only:
    
    17
    +    - master
    
    18
    +
    
    19
    +clone_depth: 1
    
    20
    +
    
    21
    +# init:
    
    22
    +#   - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
    
    23
    +
    
    24
    +install:
    
    25
    +  - set PATH=C:\msys64\usr\bin;%PATH%
    
    26
    +  - bash -lc "pacman --needed --noconfirm -Syu"
    
    27
    +  # This is needed because without killing all processes -Su will fail
    
    28
    +  - ps: Get-Process | Where-Object {$_.path -like 'C:\msys64*'} | Stop-Process
    
    29
    +  - bash -lc "pacman -Sydd --noconfirm filesystem"
    
    30
    +  - bash -lc "pacman --needed --noconfirm -Su"
    
    31
    +
    
    32
    +build_script:
    
    33
    +
    
    34
    +  - ps: |
    
    35
    +        $filename = "Gajim-$($env:GAJIM_VERSION)-$($env:ARCH)"
    
    36
    +        $filename_portable = "Gajim-$($env:GAJIM_VERSION)-Portable-$($env:ARCH)"
    
    37
    +
    
    38
    +        if ($env:GAJIM_VERSION -eq "Nightly") {
    
    39
    +            $time_string=(get-date -UFormat "%Y-%m-%d").ToString()
    
    40
    +            $filename = $filename + "-" + $time_string
    
    41
    +            $filename_portable = $filename_portable + "-" + $time_string
    
    42
    +        }
    
    43
    +
    
    44
    +        $buildroot="C:\msys64\home\appveyor\gajim\win\_build_root"
    
    45
    +
    
    46
    +        function bash($command) {
    
    47
    +          Write-Host $command -NoNewline
    
    48
    +          C:\msys64\usr\bin\sh.exe --login -c $command
    
    49
    +        }
    
    50
    +
    
    51
    +        bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
    
    52
    +        bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)"
    
    53
    +        Push-AppveyorArtifact "$($buildroot)/Gajim.exe" -FileName "$($filename).exe"
    
    54
    +        Push-AppveyorArtifact "$($buildroot)/Gajim-Portable.exe" -FileName "$($filename_portable).exe"
    
    55
    +
    
    56
    +# on_finish:
    
    57
    +#   - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

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