Skip to content

Commit

Permalink
Merge pull request #142 from Rechi/check/reverseDependencies
Browse files Browse the repository at this point in the history
add reverse dependencies check
  • Loading branch information
Rechi authored Jan 4, 2019
2 parents 710c192 + 099d385 commit 9da946f
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 99 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ It can also be used locally for detecting problems in your addons.

- Check if files in addon are marked as executable or not.

- Check for unused script.module addons

All of the validation and checks are done according to the kodi [addon rules](https://kodi.wiki/view/Add-on_rules)

## Installation
Expand Down
30 changes: 30 additions & 0 deletions kodi_addon_checker/addons/Addon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Copyright (C) 2018 Team Kodi
This file is part of Kodi - kodi.tv
SPDX-License-Identifier: GPL-3.0-only
See LICENSES/README.md for more information.
"""

from .AddonDependency import AddonDependency

import xml.etree.ElementTree as ET


class Addon(object):
def __init__(self, addon_xml: ET.Element):
super(Addon, self).__init__()
self.id = addon_xml.get('id')
self.version = addon_xml.get('version')
self.dependencies = []
for dependency in addon_xml.findall('./requires/import'):
self.dependencies.append(AddonDependency(dependency))

def __eq__(self, other):
return self.id == other.id and self.version == other.version

def dependsOn(self, addonId):
for dependency in self.dependencies:
if dependency.id == addonId:
return True
return False
20 changes: 20 additions & 0 deletions kodi_addon_checker/addons/AddonDependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Copyright (C) 2018 Team Kodi
This file is part of Kodi - kodi.tv
SPDX-License-Identifier: GPL-3.0-only
See LICENSES/README.md for more information.
"""

from distutils.version import LooseVersion
import xml.etree.ElementTree as ET


class AddonDependency(object):
def __init__(self, import_xml: ET.Element):
super(AddonDependency, self).__init__()
self.id = import_xml.get('addon')
self.version = None
if import_xml.get('version') is not None:
self.version = LooseVersion(import_xml.get('version'))
self.optional = import_xml.get('optional', False)
47 changes: 47 additions & 0 deletions kodi_addon_checker/addons/Repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Copyright (C) 2018 Team Kodi
This file is part of Kodi - kodi.tv
SPDX-License-Identifier: GPL-3.0-only
See LICENSES/README.md for more information.
"""

from .Addon import Addon

import gzip
import requests
import xml.etree.ElementTree as ET
from io import BytesIO


class Repository(object):
def __init__(self, version, path):
super(Repository, self).__init__()
self.version = version
self.path = path
gz_file = requests.get(path, timeout=(10, 10)).content
with gzip.open(BytesIO(gz_file), 'rb') as xml_file:
content = xml_file.read()
tree = ET.fromstring(content)
self.addons = []
for addon in tree.findall("addon"):
self.addons.append(Addon(addon))

def __contains__(self, addonId):
for addon in self.addons:
if addon.id == addonId:
return True
return False

def find(self, addonId):
for addon in self.addons:
if addon.id == addonId:
return addon
return None

def rdepends(self, addonId):
rdepends = []
for addon in self.addons:
if addon.dependsOn(addonId):
rdepends.append(addon)
return rdepends
7 changes: 7 additions & 0 deletions kodi_addon_checker/addons/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Copyright (C) 2018 Team Kodi
This file is part of Kodi - kodi.tv
SPDX-License-Identifier: GPL-3.0-only
See LICENSES/README.md for more information.
"""
28 changes: 4 additions & 24 deletions kodi_addon_checker/check_addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@

import os
import xml.etree.ElementTree as ET
import requests
import logging
import gzip
from io import BytesIO

from .addons.Repository import Repository
from .record import Record, INFORMATION
from .report import Report
from . import check_artwork
Expand Down Expand Up @@ -55,6 +53,8 @@ def start(addon_path, branch_name, all_repo_addons, pr, config=None):

check_dependencies.check_addon_dependencies(addon_report, repo_addons, parsed_xml, branch_name)

check_dependencies.check_reverse_dependencies(addon_report, addon_id, branch_name, all_repo_addons)

check_files.check_file_permission(addon_report, file_index)

check_files.check_for_invalid_xml_files(addon_report, file_index)
Expand Down Expand Up @@ -112,26 +112,6 @@ def all_repo_addons():

for branch in branches:
branch_url = ROOT_URL.format(branch=branch)
repo_addons[branch] = _get_addons(branch_url)
repo_addons[branch] = Repository(branch, branch_url)

return repo_addons


def _get_addons(xml_url: str):
"""Gets addon.xml file for all the version of kodi
:xml_url: url of the version of kodi
"""
try:
gz_file = requests.get(xml_url, timeout=(10, 10)).content
with gzip.open(BytesIO(gz_file), 'rb') as xml_file:
content = xml_file.read()
tree = ET.fromstring(content)

return {
a.get("id"): a.get("version")
for a in tree.findall("addon")
}
except requests.exceptions.ReadTimeout as errrt:
LOGGER.error(errrt)
except requests.exceptions.ConnectTimeout as errct:
LOGGER.error(errct)
123 changes: 67 additions & 56 deletions kodi_addon_checker/check_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
See LICENSES/README.md for more information.
"""

from .addons.Addon import Addon

import logging
from distutils.version import LooseVersion

Expand Down Expand Up @@ -39,49 +41,73 @@ def check_addon_dependencies(report: Report, repo_addons: dict, parsed_xml, bran
:branch_name: name of the kodi branch/version
"""

optional_deps, deps = _get_users_dependencies(parsed_xml)
addon = Addon(parsed_xml)
ignore = _get_ignore_list(branch_name)

if optional_deps:
for dependency, version in optional_deps.items():
if dependency not in repo_addons:
report.add(Record(INFORMATION,
"Optional dependency %s with version %s is not present in Kodi repository" %
(dependency, version)))
else:
deps_version = repo_addons[dependency]
if deps_version is not None:
if LooseVersion(version) > LooseVersion(deps_version) and (dependency not in ignore):
report.add(INFORMATION,
"version mismatch for optional dependencies: %s. Available: %s, Required: %s" %
(dependency, deps_version, version))

for required_addon, required_version in deps.items():
if required_addon not in repo_addons:
if required_addon not in ignore:
report.add(Record(
PROBLEM, "Required addon %s not available in current repository." % required_addon))
elif required_addon in VERSION_ATTRB:
try:
version = VERSION_ATTRB[required_addon][branch_name]
if LooseVersion(version) != LooseVersion(required_version):
report.add(Record(WARNING, "For %s it is advised to set %s version to %s" %
(branch_name, required_addon, version)))
except KeyError:
LOGGER.warn("Misconfiguration in VERSION_ATTRB of check_dependencies")

else:
available_version = repo_addons[required_addon]

if required_version is None:
report.add(Record(WARNING, "Required addon %s does not require a fixed version Available: %s "
% (required_addon, available_version)))
elif available_version is None:
report.add(Record(PROBLEM, "Version of %s in required version %s not available"
% (required_addon, required_version)))
elif LooseVersion(available_version) < LooseVersion(required_version) and (required_addon not in ignore):
report.add(Record(PROBLEM, "Version mismatch for addon %s. Required: %s, Available: %s "
% (required_addon, required_version, available_version)))
for dependency in addon.dependencies:
if dependency.id in ignore and not dependency.optional:
pass

elif dependency.id not in repo_addons:
report.add(Record(INFORMATION if dependency.optional else PROBLEM,
"{} dependency {} is not available in current repository"
.format("Optional" if dependency.optional else "Required", dependency.id)))

elif dependency.version is None:
report.add(Record(INFORMATION if dependency.optional else WARNING,
"{} dependency {} does not require a minimum version, available: {}"
.format("Optional" if dependency.optional else "Required", dependency.id,
repo_addons.find(dependency.id).version)))

elif repo_addons.find(dependency.id).version < dependency.version:
report.add(Record(INFORMATION if dependency.optional else PROBLEM,
"Version mismatch for {} dependency {}, required: {}, Available: {}"
.format("optional" if dependency.optional else "required", dependency.id,
dependency.version, repo_addons.find(dependency.id).version)))

if dependency.id in VERSION_ATTRB:
try:
version = VERSION_ATTRB[dependency.id][branch_name]
if LooseVersion(version) != dependency.version:
report.add(Record(WARNING, "For {} it is advised to set {} version to {}"
.format(branch_name, dependency.id, version)))
except KeyError:
LOGGER.warn("Misconfiguration in VERSION_ATTRB of check_dependencies")


def check_reverse_dependencies(report: Report, addon: str, branch_name: str, all_repo_addons: dict):
addonInRepo = None
rdepends = []
rdependsLowerBranch = []
branchFound = False

for branch, repo in sorted(all_repo_addons.items()):
if not branchFound and branch != branch_name:
for rdepend in repo.rdepends(addon):
if rdepend not in rdependsLowerBranch:
rdependsLowerBranch.append(rdepend)
continue
branchFound = True

addonFind = repo.find(addon)
if addonFind and addonInRepo and addonFind != addonInRepo:
break

addonInRepo = addonFind

for rdepend in repo.rdepends(addon):
if rdepend not in rdependsLowerBranch and rdepend not in rdepends:
rdepends.append(rdepend)
if addon.startswith("script.module.") and len(rdepends) + len(rdependsLowerBranch) == 0:
report.add(Record(WARNING, "This module isn't required by any add-on."))

if len(rdepends) > 0:
report.add(Record(INFORMATION, "Reverse dependencies: {} ({})"
.format(", ".join(sorted([r.id for r in rdepends])), len(rdepends))))

if len(rdependsLowerBranch) > 0:
report.add(Record(INFORMATION, "Reverse dependencies (in lower branches): {} ({})"
.format(", ".join(sorted([r.id for r in rdependsLowerBranch])), len(rdependsLowerBranch))))


def _get_ignore_list(branch_name):
Expand All @@ -96,18 +122,3 @@ def _get_ignore_list(branch_name):

else:
return common_ignore_deps


def _get_users_dependencies(parsed_xml):
"""Gets all the dependencies from a given addon
:parsed_xml: parsed addon.xml
"""
user_deps = {}
optional_deps = {}
for i in parsed_xml.findall("requires/import"):
if i.get("optional"):
optional_deps[i.get("addon")] = i.get("version")
else:
user_deps[i.get("addon")] = i.get("version")

return optional_deps, user_deps
8 changes: 3 additions & 5 deletions kodi_addon_checker/check_old_addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ def check_for_existing_addon(report: Report, addon_path: str, all_repo_addons: d
addon_xml = os.path.join(addon_path, "addon.xml")
addon_name, addon_version = _get_addon_name(addon_xml)

for branch in sorted(all_repo_addons):
repo_addons = all_repo_addons[branch]

if addon_name in repo_addons:
_check_versions(report, addon_name, branch, addon_version, repo_addons[addon_name], pr)
for branch, repo in sorted(all_repo_addons.items()):
if addon_name in repo:
_check_versions(report, addon_name, branch, addon_version, repo.find(addon_name).version, pr)
return

report.add(Record(INFORMATION, "This is a new addon"))
Expand Down
14 changes: 0 additions & 14 deletions script.test/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from radon.raw import analyze
from distutils.version import LooseVersion
import xml.etree.ElementTree as ET
import requests

from PIL import Image

Expand Down Expand Up @@ -358,19 +357,6 @@ def number_of_lines(filepath):
return (analyze(data).lloc)


def _get_addons(xml_url):
"""
addon.xml for the target Kodi version
"""
content = requests.get(xml_url).content
tree = ET.fromstring(content)

return {
a.get("id"): a.get("version")
for a in tree.findall("addon")
}


def _get_users_dependencies(addon_path):
"""
User's addon.xml from pull request
Expand Down

0 comments on commit 9da946f

Please sign in to comment.