blob: d2715bc6ebf3fff4c927bfbd47b58ff7fd70cc21 [file] [log] [blame]
# -*- coding:utf-8 -*-
from __future__ import print_function, unicode_literals
import re
import stat
from _emerge.Package import Package
from _emerge.RootConfig import RootConfig
from repoman.modules.scan.scanbase import ScanBase
# import our initialized portage instance
from repoman._portage import portage
from portage import os
from portage.const import LIVE_ECLASSES
from portage.exception import InvalidPackageName
pv_toolong_re = re.compile(r'[0-9]{19,}')
class Ebuild(ScanBase):
'''Class to run primary checks on ebuilds'''
def __init__(self, **kwargs):
'''Class init
@param qatracker: QATracker instance
@param portdb: portdb instance
@param repo_settings: repository settings instance
@param vcs_settings: VCSSettings instance
@param checks: checks dictionary
'''
super(Ebuild, self).__init__(**kwargs)
self.qatracker = kwargs.get('qatracker')
self.portdb = kwargs.get('portdb')
self.repo_settings = kwargs.get('repo_settings')
self.vcs_settings = kwargs.get('vcs_settings')
self.checks = kwargs.get('checks')
self.root_config = RootConfig(self.repo_settings.repoman_settings,
self.repo_settings.trees[self.repo_settings.root], None)
self.changed = None
self.xpkg = None
self.y_ebuild = None
self.pkg = None
self.metadata = None
self.eapi = None
self.inherited = None
self.live_ebuild = None
self.keywords = None
self.pkgs = {}
def _set_paths(self, **kwargs):
repolevel = kwargs.get('repolevel')
self.relative_path = os.path.join(self.xpkg, self.y_ebuild + ".ebuild")
self.full_path = os.path.join(self.repo_settings.repodir, self.relative_path)
self.ebuild_path = self.y_ebuild + ".ebuild"
if repolevel < 3:
self.ebuild_path = os.path.join(kwargs.get('pkgdir'), self.ebuild_path)
if repolevel < 2:
self.ebuild_path = os.path.join(kwargs.get('catdir'), self.ebuild_path)
self.ebuild_path = os.path.join(".", self.ebuild_path)
@property
def untracked(self):
'''Determines and returns if the ebuild is not tracked by the vcs'''
do_check = self.vcs_settings.vcs in ("cvs", "svn", "bzr")
really_notadded = (self.checks['ebuild_notadded'] and
self.y_ebuild not in self.vcs_settings.eadded)
if do_check and really_notadded:
# ebuild not added to vcs
return True
return False
def check(self, **kwargs):
'''Perform a changelog and untracked checks on the ebuild
@param xpkg: Package in which we check (object).
@param y_ebuild: Ebuild which we check (string).
@param changed: dictionary instance
@param repolevel: The depth within the repository
@param catdir: The category directiory
@param pkgdir: the package directory
@returns: dictionary, including {ebuild object}
'''
self.xpkg = kwargs.get('xpkg')
self.y_ebuild = kwargs.get('y_ebuild')
self.changed = kwargs.get('changed')
changelog_modified = kwargs.get('changelog_modified')
self._set_paths(**kwargs)
if self.checks['changelog'] and not changelog_modified \
and self.ebuild_path in self.changed.new_ebuilds:
self.qatracker.add_error('changelog.ebuildadded', self.relative_path)
if self.untracked:
# ebuild not added to vcs
self.qatracker.add_error(
"ebuild.notadded", self.xpkg + "/" + self.y_ebuild + ".ebuild")
# update the dynamic data
dyn_ebuild = kwargs.get('ebuild')
dyn_ebuild.set(self)
return False
def set_pkg_data(self, **kwargs):
'''Sets some classwide data needed for some of the checks
@returns: dictionary
'''
self.pkg = self.pkgs[self.y_ebuild]
self.metadata = self.pkg._metadata
self.eapi = self.metadata["EAPI"]
self.inherited = self.pkg.inherited
self.live_ebuild = LIVE_ECLASSES.intersection(self.inherited)
self.keywords = self.metadata["KEYWORDS"].split()
self.archs = set(kw.lstrip("~") for kw in self.keywords if not kw.startswith("-"))
return False
def bad_split_check(self, **kwargs):
'''Checks for bad category/package splits.
@param pkgdir: string: path
@returns: dictionary
'''
pkgdir = kwargs.get('pkgdir')
myesplit = portage.pkgsplit(self.y_ebuild)
is_bad_split = myesplit is None or myesplit[0] != self.xpkg.split("/")[-1]
if is_bad_split:
is_pv_toolong = pv_toolong_re.search(myesplit[1])
is_pv_toolong2 = pv_toolong_re.search(myesplit[2])
if is_pv_toolong or is_pv_toolong2:
self.qatracker.add_error(
"ebuild.invalidname", self.xpkg + "/" + self.y_ebuild + ".ebuild")
return True
elif myesplit[0] != pkgdir:
print(pkgdir, myesplit[0])
self.qatracker.add_error(
"ebuild.namenomatch", self.xpkg + "/" + self.y_ebuild + ".ebuild")
return True
return False
def pkg_invalid(self, **kwargs):
'''Sets some pkg info and checks for invalid packages
@param validity_future: Future instance
@returns: dictionary, including {pkg object}
'''
fuse = kwargs.get('validity_future')
dyn_pkg = kwargs.get('pkg')
if self.pkg.invalid:
for k, msgs in self.pkg.invalid.items():
for msg in msgs:
self.qatracker.add_error(k, "%s: %s" % (self.relative_path, msg))
# update the dynamic data
fuse.set(False, ignore_InvalidState=True)
dyn_pkg.set(self.pkg)
return True
# update the dynamic data
dyn_pkg.set(self.pkg)
return False
def check_isebuild(self, **kwargs):
'''Test the file for qualifications that is is an ebuild
@param checkdirlist: list of files in the current package directory
@param checkdir: current package directory path
@param xpkg: current package directory being checked
@param validity_future: Future instance
@returns: dictionary, including {pkgs, can_force}
'''
checkdirlist = kwargs.get('checkdirlist').get()
checkdir = kwargs.get('checkdir')
xpkg = kwargs.get('xpkg')
fuse = kwargs.get('validity_future')
can_force = kwargs.get('can_force')
self.continue_ = False
ebuildlist = []
pkgs = {}
for y in checkdirlist:
file_is_ebuild = y.endswith(".ebuild")
file_should_be_non_executable = (
y in self.repo_settings.qadata.no_exec or file_is_ebuild)
if file_should_be_non_executable:
file_is_executable = stat.S_IMODE(
os.stat(os.path.join(checkdir, y)).st_mode) & 0o111
if file_is_executable:
self.qatracker.add_error("file.executable", os.path.join(checkdir, y))
if file_is_ebuild:
pf = y[:-7]
ebuildlist.append(pf)
catdir = xpkg.split("/")[0]
cpv = "%s/%s" % (catdir, pf)
allvars = self.repo_settings.qadata.allvars
try:
myaux = dict(zip(allvars, self.portdb.aux_get(cpv, allvars)))
except KeyError:
fuse.set(False, ignore_InvalidState=True)
self.qatracker.add_error("ebuild.syntax", os.path.join(xpkg, y))
continue
except IOError:
fuse.set(False, ignore_InvalidState=True)
self.qatracker.add_error("ebuild.output", os.path.join(xpkg, y))
continue
except InvalidPackageName:
fuse.set(False, ignore_InvalidState=True)
self.qatracker.add_error("ebuild.invalidname", os.path.join(xpkg, y))
continue
if not portage.eapi_is_supported(myaux["EAPI"]):
fuse.set(False, ignore_InvalidState=True)
self.qatracker.add_error("EAPI.unsupported", os.path.join(xpkg, y))
continue
pkgs[pf] = Package(
cpv=cpv, metadata=myaux, root_config=self.root_config,
type_name="ebuild")
if len(pkgs) != len(ebuildlist):
# If we can't access all the metadata then it's totally unsafe to
# commit since there's no way to generate a correct Manifest.
# Do not try to do any more QA checks on this package since missing
# metadata leads to false positives for several checks, and false
# positives confuse users.
self.continue_ = True
can_force.set(False, ignore_InvalidState=True)
self.pkgs = pkgs
# set our updated data
dyn_pkgs = kwargs.get('pkgs')
dyn_pkgs.set(pkgs)
return self.continue_
@property
def runInPkgs(self):
'''Package level scans'''
return (True, [self.check_isebuild])
@property
def runInEbuilds(self):
'''Ebuild level scans'''
return (True, [self.check, self.set_pkg_data, self.bad_split_check, self.pkg_invalid])