Merge pull request #523 from h1dden-da3m0n/ci/migrate-to-actions

ci: migrate Azure Pipelines to GitHub Actions
This commit is contained in:
mcarlton00 2021-06-03 18:37:57 -04:00 committed by GitHub
commit 399fc32b60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 503 additions and 271 deletions

View file

@ -1,35 +0,0 @@
trigger:
batch: true
branches:
include:
- '*'
tags:
include:
- '*'
jobs:
- job: 'Validate'
pool:
vmImage: 'ubuntu-18.04'
strategy:
matrix:
Python27:
python.version: '2.7'
Python36:
python.version: '3.6'
steps:
# Run tests and linting
- template: validate.yml
- job: Build
steps:
# On every PR, build the addon and make it available for download as an artifact
- template: build.yml
parameters:
py_versions: [ 'py2', 'py3' ]
# When triggered by a tag, publish the built addon to the repo server
- ${{ if startsWith(variables['Build.SourceBranch'], 'refs/tags') }}:
- template: publish.yml
parameters:
py_versions: [ 'py2', 'py3' ]

View file

@ -1,46 +0,0 @@
parameters:
python_versions : []
steps:
- ${{ each py_version in parameters.py_versions }}:
- task: usePythonVersion@0
inputs:
versionSpec: '3.6'
- checkout: self
clean: true
- script: python3 -m pip install --user pyyaml
displayName: 'Install PyYaml'
- script: python3 jellyfin-kodi/.config/generate_xml.py ${{ py_version }}
displayName: 'Create ${{ py_version }} addon.xml'
- task: CopyFiles@2
displayName: 'Create clean addon directory'
inputs:
sourceFolder: 'jellyfin-kodi'
cleanTargetFolder: true
contents: |
**/*
!.ci/*
!.config/*
!.git/**/*
!.github/*
TargetFolder: '$(Build.ArtifactStagingDirectory)/plugin.video.jellyfin'
- task: ArchiveFiles@2
displayName: 'Create ${{ py_version }} zip file'
inputs:
rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/plugin.video.jellyfin'
includeRootFolder: True
archiveType: 'zip'
tarCompression: 'none'
archiveFile: '$(Build.ArtifactStagingDirectory)/plugin.video.jellyfin-${{ py_version }}.zip'
- task: PublishPipelineArtifact@1
displayName: 'Publish ${{ py_version }} artifact'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/plugin.video.jellyfin'
artifactName: 'plugin.video.jellyfin-${{ py_version }}-$(Build.BuildNumber)'

View file

@ -1,27 +0,0 @@
parameters:
python_version : []
steps:
- ${{ each py_version in parameters.py_versions }}:
- task: CopyFilesOverSSH@0
displayName: 'Upload to repo server'
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.ArtifactStagingDirectory)'
contents: 'plugin.video.jellyfin-${{ py_version }}.zip'
targetFolder: '/srv/repository/incoming/kodi'
- task: SSH@0
displayName: 'Add to Kodi repo'
inputs:
sshEndpoint: repository
runOptions: 'commands'
commands: 'python3 /usr/local/bin/kodirepo add /srv/repository/incoming/kodi/plugin.video.jellyfin-${{ py_version }}.zip --datadir /srv/repository/releases/client/kodi/${{ py_version }}'
failOnStdErr: false
- task: SSH@0
displayName: 'Clean up zip files'
inputs:
sshEndpoint: repository
runOptions: 'commands'
commands: 'rm /srv/repository/incoming/kodi/plugin.video.jellyfin-${{ py_version }}.zip'

View file

@ -1,58 +0,0 @@
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
- script: python -m pip install -r requirements-dev.txt
displayName: 'Install dev tools'
- script: |
# Azure pipelines: https://github.com/microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
# GitHub actions: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions
rm -f flake8.output
flake8 . --statistics --output-file=flake8.output && {
echo "##vso[task.complete result=Succeeded;]"
} || {
echo "##vso[task.complete result=Failed;]"
}
cat flake8.output | awk -F: '{
if ($0 ~ /^\./) {
gsub(/^[ \t]+/,"",$4);
codesep=index($4, " ");
code=substr($4, 1, codesep-1);
msg=substr($4, codesep+1);
print "##vso[task.logissue type=error;sourcepath=" $1 ";linenumber=" $2 ";columnnumber=" $3 ";code=" code ";]" msg;
} else {
print $0
}
}'
displayName: 'Run Linter'
failOnStderr: false
- script: |
rm -f test.xml coverage.xml
coverage run && {
echo "##vso[task.complete result=Succeeded;]"
} || {
echo "##vso[task.complete result=Failed;]"
}
coverage xml
coverage html
coverage report
displayName: 'Run Tests'
failOnStderr: false
condition: succeededOrFailed()
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: 'test.xml'
testRunTitle: 'Publish test results for Python $(python.version)'
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
inputs:
codeCoverageTool: 'cobertura'
summaryFileLocation: 'coverage.xml'

View file

@ -1,70 +0,0 @@
import xml.etree.ElementTree as ET
import sys
import os
from datetime import datetime
import yaml
def indent(elem, level=0):
'''
Nicely formats output xml with newlines and spaces
https://stackoverflow.com/a/33956544
'''
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
try:
py_version = sys.argv[1]
except IndexError:
print('No version specified')
sys.exit(1)
dir_path = os.path.dirname(os.path.realpath(__file__))
# Load template file
with open('{dir_path}/template.xml'.format(**locals()), 'r') as f:
tree = ET.parse(f)
root = tree.getroot()
# Load version dependencies
with open('{dir_path}/{py_version}.yaml'.format(**locals()), 'r') as f:
deps = yaml.safe_load(f)
# Load version and changelog
with open('jellyfin-kodi/release.yaml', 'r') as f:
data = yaml.safe_load(f)
# Populate xml template
for dep in deps:
ET.SubElement(root.find('requires'), 'import', attrib=dep)
# Update version string
addon_version = data.get('version')
root.attrib['version'] = '{addon_version}+{py_version}'.format(**locals())
# Changelog
date = datetime.today().strftime('%Y-%m-%d')
changelog = data.get('changelog')
for section in root.findall('extension'):
news = section.findall('news')
if news:
news[0].text = 'v{addon_version} ({date}):\n{changelog}'.format(**locals())
# Format xml tree
indent(root)
# Write addon.xml
tree.write('jellyfin-kodi/addon.xml', encoding='utf-8', xml_declaration=True)

View file

@ -1,16 +0,0 @@
- addon: 'xbmc.python'
version: '2.25.0'
- addon: 'script.module.requests'
version: '2.22.0'
- addon: 'script.module.dateutil'
version: '2.8.1'
- addon: 'script.module.six'
version: '1.13.0'
- addon: 'script.module.kodi-six'
version: '0.0.7'
- addon: 'script.module.addon.signals'
version: '0.0.5'
- addon: 'script.module.futures'
version: '2.2.0'
- addon: 'script.module.websocket'
version: '0.57.0'

View file

@ -1,14 +0,0 @@
- addon: 'xbmc.python'
version: '3.0.0'
- addon: 'script.module.requests'
version: '2.22.0+matrix.1'
- addon: 'script.module.dateutil'
version: '2.8.1+matrix.1'
- addon: 'script.module.six'
version: '1.14.0+matrix.2'
- addon: 'script.module.kodi-six'
version: '0.1.3+1'
- addon: 'script.module.addon.signals'
version: '0.0.5+matrix.1'
- addon: 'script.module.websocket'
version: '0.58.0+matrix.1'

View file

@ -14,7 +14,7 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = null
max_line_length = 9999
# YAML indentation
[*.{yml,yaml}]

18
.github/dependabot.yaml vendored Normal file
View file

@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
labels:
- ci
- github-actions
- dependencies
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
labels:
- ci
- pip
- dependencies

22
.github/release-drafter.yml vendored Normal file
View file

@ -0,0 +1,22 @@
_extends: jellyfin/jellyfin-meta-plugins
name-template: "Release $RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
version-template: "$MAJOR.$MINOR.$PATCH"
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## :sparkles: What's New
$CHANGES

39
.github/workflows/build.yaml vendored Normal file
View file

@ -0,0 +1,39 @@
name: Build Jellyfin-Kodi
on:
push:
branches:
- master
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
py_version: [ 'py2', 'py3' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pyyaml
- name: Create ${{ matrix.py_version }} addon.xml
run: python build.py --version ${{ matrix.py_version }}
- name: Publish Build Artifact
uses: actions/upload-artifact@v2
with:
retention-days: 14
name: ${{ matrix.py_version }}-build-artifact
path: |
*.zip

41
.github/workflows/codeql.yaml vendored Normal file
View file

@ -0,0 +1,41 @@
name: CodeQL Analysis
on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
- cron: '38 8 * * 6'
jobs:
analyze:
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-kodi' }}
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
version: ['2.7', '3.9']
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.version }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -0,0 +1,66 @@
name: Create Release PR
on:
workflow_dispatch:
jobs:
create_pr:
name: "Create Pump Version PR"
runs-on: ubuntu-latest
steps:
- name: Update Draft
uses: release-drafter/release-drafter@v5.15.0
id: draft
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
- name: Setup YQ
uses: chrisdickinson/setup-yq@latest
with:
yq-version: v4.9.1
- name: Parse Changlog
run: |
cat << EOF >> cl.md
${{ steps.draft.outputs.body }}
EOF
TAG="${{ steps.draft.outputs.tag_name }}"
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
echo "CHANGELOG=`cat cl.md | grep '*'`" >> $GITHUB_ENV
rm cl.md
- name: Checkout repository
uses: actions/checkout@v2
- name: Update release.yaml
run: |
yq eval '.version = env(VERSION) | .changelog = strenv(CHANGELOG) | .changelog style="literal"' -i release.yaml
- name: Commit Changes
run: |
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b prepare-${{ env.VERSION }}
git commit -am "bump version to ${{ env.VERSION }}"
if [[ -z "$(git ls-remote --heads origin prepare-${{ env.VERSION }})" ]]; then
git push origin prepare-${{ env.VERSION }}
else
git push -f origin prepare-${{ env.VERSION }}
fi
- name: Create or Update PR
uses: k3rnels-actions/pr-update@v1
with:
token: ${{ secrets.JF_BOT_TOKEN }}
pr_title: Prepare for release ${{ steps.draft.outputs.tag_name }}
pr_source: prepare-${{ env.VERSION }}
pr_labels: 'release-prep,skip-changelog'
pr_body: |
:robot: This is a generated PR to bump the `release.yaml` version and update the changelog.
---
${{ env.CHANGELOG }}

64
.github/workflows/publish.yaml vendored Normal file
View file

@ -0,0 +1,64 @@
name: Publish Jellyfin-Kodi
on:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
strategy:
matrix:
py_version: [ 'py2', 'py3' ]
steps:
- name: Update Draft
uses: release-drafter/release-drafter@v5.15.0
with:
publish: true
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pyyaml
- name: Create ${{ matrix.py_version }} addon.xml
run: python build.py --version ${{ matrix.py_version }}
- name: Publish Build Artifact
uses: actions/upload-artifact@v2
with:
retention-days: 14
name: ${{ matrix.py_version }}-build-artifact
path: |
*.zip
- name: Upload to repo server
uses: burnett01/rsync-deployments@4.1
with:
switches: -vrptz
path: '*.zip'
remote_path: /srv/repository/incoming/kodi
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
- name: Add to Kodi repo and clean up
uses: appleboy/ssh-action@v0.1.4
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
envs: JELLYFIN_VERSION
script_stop: true
script: |
python3 /usr/local/bin/kodirepo add /srv/repository/incoming/kodi/plugin.video.jellyfin+${{ matrix.py_version }}.zip --datadir /srv/repository/releases/client/kodi/${{ matrix.py_version }};
rm /srv/repository/incoming/kodi/plugin.video.jellyfin+${{ matrix.py_version }}.zip;

16
.github/workflows/release-drafter.yaml vendored Normal file
View file

@ -0,0 +1,16 @@
name: Release Drafter
on:
push:
branches:
- master
jobs:
update_release_draft:
name: Update release draft
runs-on: ubuntu-latest
steps:
- name: Update Release Draft
uses: release-drafter/release-drafter@v5.15.0
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

73
.github/workflows/test.yaml vendored Normal file
View file

@ -0,0 +1,73 @@
name: Test Jellyfin-Kodi
on:
push:
branches:
- master
pull_request_target:
branches:
- master
env:
PR_TRIGGERED: ${{ github.event_name == 'pull_request' && github.repository == 'jellyfin/jellyfin-kodi' }}
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
py_version: ['2.7', '3.9']
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.py_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.py_version }}
- name: Setup reviewdog
uses: reviewdog/action-setup@v1
if: ${{ env.PR_TRIGGERED == 'true' && matrix.py_version == '3.9' }}
with:
reviewdog_version: nightly
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --output-file=flake8.output
cat flake8.output
- name: Test with Coverage
run: |
coverage run
coverage xml
coverage report
- name: Run reviewdog for PR checks-api
if: ${{ env.PR_TRIGGERED == 'true' && matrix.py_version == '3.9' }}
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |
cat flake8.output | reviewdog -reporter=github-pr-check -f=flake8 -name="flake8"
- name: Upload coverage
uses: codecov/codecov-action@v1
if: ${{ matrix.py_version == '3.9' }}
- name: Publish Test Atrifact
uses: actions/upload-artifact@v2
with:
retention-days: 14
name: ${{ matrix.py_version }}-test-results
path: |
flake8.output
test.xml
coverage.xml

125
build.py Normal file
View file

@ -0,0 +1,125 @@
#!/usr/bin/env python
import argparse
import os
import xml.etree.ElementTree as ET
import zipfile
from datetime import datetime
from pathlib import Path
import yaml
def indent(elem: ET.Element, level: int = 0) -> None:
"""
Nicely formats output xml with newlines and spaces
https://stackoverflow.com/a/33956544
"""
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def create_addon_xml(config: dict, source: str, py_version: str) -> None:
"""
Create addon.xml from template file
"""
# Load template file
with open('{}/.build/template.xml'.format(source), 'r') as f:
tree = ET.parse(f)
root = tree.getroot()
# Populate dependencies in template
dependencies = config['dependencies'].get(py_version)
for dep in dependencies:
ET.SubElement(root.find('requires'), 'import', attrib=dep)
# Populate version string
addon_version = config.get('version')
root.attrib['version'] = '{}+{}'.format(addon_version, py_version)
# Populate Changelog
date = datetime.today().strftime('%Y-%m-%d')
changelog = config.get('changelog')
for section in root.findall('extension'):
news = section.findall('news')
if news:
news[0].text = 'v{} ({}):\n{}'.format(addon_version, date, changelog)
# Format xml tree
indent(root)
# Write addon.xml
tree.write('{}/addon.xml'.format(source), encoding='utf-8', xml_declaration=True)
def zip_files(py_version: str, source: str, target: str) -> None:
"""
Create installable addon zip archive
"""
archive_name = 'plugin.video.jellyfin+{}.zip'.format(py_version)
with zipfile.ZipFile('{}/{}'.format(target, archive_name), 'w') as z:
for root, dirs, files in os.walk(args.source):
for filename in filter(file_filter, files):
file_path = os.path.join(root, filename)
if folder_filter(file_path):
relative_path = os.path.join('plugin.video.jellyfin', os.path.relpath(file_path, source))
z.write(file_path, relative_path)
def file_filter(file_name: str) -> bool:
"""
True if file_name is meant to be included
"""
return 'plugin.video.jellyfin' not in file_name and 'pyo' not in file_name
def folder_filter(folder_name: str) -> bool:
"""
True if folder_name is meant to be included
"""
return ('.ci' not in folder_name
and '.git' not in folder_name
and '.github' not in folder_name
and '.build' not in folder_name)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Build flags:')
parser.add_argument(
'--version',
type=str,
choices=('py2', 'py3'),
default='py3')
parser.add_argument(
'--source',
type=Path,
default=Path(__file__).absolute().parent)
parser.add_argument(
'--target',
type=Path,
default=Path(__file__).absolute().parent)
args = parser.parse_args()
# Load config file
config_path = os.path.join(args.source, 'release.yaml')
with open(config_path, 'r') as fh:
release_config = yaml.safe_load(fh)
create_addon_xml(release_config, args.source, args.version)
zip_files(args.version, args.source, args.target)

View file

@ -2,4 +2,37 @@ version: '0.7.3'
changelog: |
- #508 Use jellyfin's DisplayTitle for audio and subtitle streams
- #513 Fix subsequent syncs from the addon menu better
- #515 Allow multiple subtitles with identical names
- #515 Allow multiple subtitles with identical names
dependencies:
py2:
- addon: 'xbmc.python'
version: '2.25.0'
- addon: 'script.module.requests'
version: '2.22.0'
- addon: 'script.module.dateutil'
version: '2.8.1'
- addon: 'script.module.six'
version: '1.13.0'
- addon: 'script.module.kodi-six'
version: '0.0.7'
- addon: 'script.module.addon.signals'
version: '0.0.5'
- addon: 'script.module.futures'
version: '2.2.0'
- addon: 'script.module.websocket'
version: '0.57.0'
py3:
- addon: 'xbmc.python'
version: '3.0.0'
- addon: 'script.module.requests'
version: '2.22.0+matrix.1'
- addon: 'script.module.dateutil'
version: '2.8.1+matrix.1'
- addon: 'script.module.six'
version: '1.14.0+matrix.2'
- addon: 'script.module.kodi-six'
version: '0.1.3+1'
- addon: 'script.module.addon.signals'
version: '0.0.5+matrix.1'
- addon: 'script.module.websocket'
version: '0.58.0+matrix.1'

View file

@ -1,7 +1,7 @@
[flake8]
max-line-length = 9999
import-order-style = pep8
exclude = ./.git,./.vscode,./libraries
exclude = .git,.vscode,libraries,build.py
extend-ignore =
I202
per-file-ignores =
@ -19,7 +19,8 @@ source =
context_play.py
default.py
service.py
.config/generate_xml.py
omit = tests/*
omit =
tests/*
build.py
branch = True
command_line = -m pytest --junitxml=test.xml