|
@@ -0,0 +1,139 @@
|
|
|
+
|
|
|
+import os
|
|
|
+import subprocess
|
|
|
+import re
|
|
|
+import tarfile
|
|
|
+import tempfile
|
|
|
+from UserDict import UserDict
|
|
|
+
|
|
|
+
|
|
|
+class InvalidPackage(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class PKGBUILD(UserDict):
|
|
|
+ """Representation of an Archlinux package"""
|
|
|
+
|
|
|
+ def __init__(self, file):
|
|
|
+ UserDict.__init__(self)
|
|
|
+ self._required_fields = (
|
|
|
+ 'name', 'description', 'version', 'release',
|
|
|
+ 'licenses', 'arch',
|
|
|
+ )
|
|
|
+ self._validated = False
|
|
|
+ self._is_valid = False
|
|
|
+ self._errors = []
|
|
|
+ self._warnings = []
|
|
|
+
|
|
|
+ self.load(file)
|
|
|
+
|
|
|
+ def load(self, file):
|
|
|
+ """Parse a PKGBUILD (can be within a tar file) and import the variables"""
|
|
|
+ if not os.path.exists(file):
|
|
|
+ raise Exception("file does not exist")
|
|
|
+ script_dir = os.path.dirname(__file__)
|
|
|
+ if not script_dir:
|
|
|
+ script_dir = os.path.abspath(script_dir)
|
|
|
+ is_temporary = False
|
|
|
+
|
|
|
+
|
|
|
+ try:
|
|
|
+ tar = tarfile.open(file, "r")
|
|
|
+ except:
|
|
|
+ if os.path.basename(file) != "PKGBUILD":
|
|
|
+ raise
|
|
|
+ else:
|
|
|
+ to_extract = None
|
|
|
+ for member in tar.getnames():
|
|
|
+ if member.find("PKGBUILD") >= 0:
|
|
|
+ to_extract = member
|
|
|
+ break
|
|
|
+ if not to_extract:
|
|
|
+ raise InvalidPackage('tar file does not contain a PKGBUILD')
|
|
|
+
|
|
|
+ directory = tempfile.mkdtemp()
|
|
|
+ tar.extract(to_extract, directory)
|
|
|
+ file = os.path.join(directory, to_extract)
|
|
|
+ is_temporary = True
|
|
|
+
|
|
|
+
|
|
|
+ working_dir = os.path.dirname(file)
|
|
|
+ if working_dir == '':
|
|
|
+ working_dir = None
|
|
|
+ filename = os.path.basename(file)
|
|
|
+
|
|
|
+
|
|
|
+ process = subprocess.Popen([
|
|
|
+ os.path.join(script_dir, 'parsepkgbuild.sh'), filename],
|
|
|
+ stdout=subprocess.PIPE, cwd=working_dir)
|
|
|
+ process.wait()
|
|
|
+
|
|
|
+
|
|
|
+ for expression in process.stdout.readlines():
|
|
|
+ exec 'temp = dict(' + expression.rstrip() + ')'
|
|
|
+ self.update(temp)
|
|
|
+
|
|
|
+
|
|
|
+ if is_temporary:
|
|
|
+ for root, dirs, files in os.walk(directory, topdown=False):
|
|
|
+ for name in files:
|
|
|
+ os.remove(os.path.join(root, name))
|
|
|
+ for name in dirs:
|
|
|
+ os.rmdir(os.path.join(root, name))
|
|
|
+ os.rmdir(directory)
|
|
|
+
|
|
|
+ def validate(self):
|
|
|
+ """Validate PKGBUILD for missing or invalid fields"""
|
|
|
+
|
|
|
+ for field in self._required_fields:
|
|
|
+ if not self[field]:
|
|
|
+ self._errors.append('%s field is required' % field)
|
|
|
+ if not re.compile("^[\w\d_-]+$").match(self['name']):
|
|
|
+ self._errors.append('package name must be alphanumeric')
|
|
|
+ elif not re.compile("[a-z\d_-]+").match(self['name']):
|
|
|
+ self._warnings.append('package name should be in lower case')
|
|
|
+ if self['version'].find('-') >= 0:
|
|
|
+ self._errors.append('version field is not allowed to contain hyphens')
|
|
|
+ if str(self['release']).find('-') >= 0:
|
|
|
+ self._errors.append('release field is not allowed to contain hyphens')
|
|
|
+
|
|
|
+ if self['description'] and len(self['description']) > 80:
|
|
|
+ self._warnings.append('description should not exceed 80 characters')
|
|
|
+
|
|
|
+ found_sums = False
|
|
|
+ for checksum in ('md5sums', 'sha1sums', 'sha256sums', 'sha384sums',
|
|
|
+ 'sha512sums'):
|
|
|
+ if self[checksum]:
|
|
|
+ found_sums = True
|
|
|
+ if len(self[checksum]) != len(self['source']):
|
|
|
+ self._errors.append('amount of %s and sources does not match'
|
|
|
+ % checksum)
|
|
|
+ if self['source'] and not found_sums:
|
|
|
+ self._errors.append('sources exist without checksums')
|
|
|
+
|
|
|
+ self._validated = True
|
|
|
+ if not self.has_errors():
|
|
|
+ self._is_valid = True
|
|
|
+
|
|
|
+ def is_valid(self):
|
|
|
+ """If Package wasn't validated already, validate and report whether
|
|
|
+ package is valid"""
|
|
|
+ if not self._validated:
|
|
|
+ self.validate()
|
|
|
+ return self._is_valid
|
|
|
+
|
|
|
+ def has_errors(self):
|
|
|
+ """Determine whether package has any errors"""
|
|
|
+ return len(self._errors) > 0
|
|
|
+
|
|
|
+ def has_warnings(self):
|
|
|
+ """Determin whether the package has any warnings"""
|
|
|
+ return len(self._warnings) > 0
|
|
|
+
|
|
|
+ def get_errors(self):
|
|
|
+ """Retrieve a list of errors"""
|
|
|
+ return self._errors
|
|
|
+
|
|
|
+ def get_warnings(self):
|
|
|
+ """Retrieve a list of warnings"""
|
|
|
+ return self._warnings
|