pkgbuild.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. #!/usr/bin/env python
  2. import os
  3. import subprocess
  4. import re
  5. import tarfile
  6. import tempfile
  7. from UserDict import UserDict
  8. class InvalidPackage(Exception):
  9. pass
  10. class PKGBUILD(UserDict):
  11. """Representation of an Archlinux package"""
  12. def __init__(self, file):
  13. UserDict.__init__(self)
  14. self._required_fields = (
  15. 'name', 'description', 'version', 'release',
  16. 'licenses', 'arch',
  17. )
  18. self._validated = False
  19. self._is_valid = False
  20. self._errors = []
  21. self._warnings = []
  22. self.load(file)
  23. def load(self, file):
  24. """Parse a PKGBUILD (can be within a tar file) and import the variables"""
  25. if not os.path.exists(file):
  26. raise Exception("file does not exist")
  27. script_dir = os.path.dirname(__file__)
  28. if not script_dir:
  29. script_dir = os.path.abspath(script_dir)
  30. is_temporary = False
  31. # Check if it's a tarballed PKGBUILD and extract it
  32. try:
  33. tar = tarfile.open(file, "r")
  34. except:
  35. if os.path.basename(file) != "PKGBUILD":
  36. raise
  37. else:
  38. to_extract = None
  39. for member in tar.getnames():
  40. if member.find("PKGBUILD") >= 0:
  41. to_extract = member
  42. break
  43. if not to_extract:
  44. raise InvalidPackage('tar file does not contain a PKGBUILD')
  45. # Create a temporary directory and extract to it
  46. directory = tempfile.mkdtemp()
  47. tar.extract(to_extract, directory)
  48. file = os.path.join(directory, to_extract)
  49. is_temporary = True
  50. # Find the current directory and filename
  51. working_dir = os.path.dirname(file)
  52. if working_dir == '':
  53. working_dir = None
  54. filename = os.path.basename(file)
  55. # Let's parse the PKGBUILD
  56. process = subprocess.Popen([
  57. os.path.join(script_dir, 'parsepkgbuild.sh'), filename],
  58. stdout=subprocess.PIPE, cwd=working_dir)
  59. process.wait()
  60. # "Import" variables into local namespace
  61. for expression in process.stdout.readlines():
  62. exec 'temp = dict(' + expression.rstrip() + ')'
  63. self.update(temp)
  64. # Remove the temporary file since we don't need it
  65. if is_temporary:
  66. for root, dirs, files in os.walk(directory, topdown=False):
  67. for name in files:
  68. os.remove(os.path.join(root, name))
  69. for name in dirs:
  70. os.rmdir(os.path.join(root, name))
  71. os.rmdir(directory)
  72. def validate(self):
  73. """Validate PKGBUILD for missing or invalid fields"""
  74. # Search for missing fields
  75. for field in self._required_fields:
  76. if not self[field]:
  77. self._errors.append('%s field is required' % field)
  78. if not re.compile("^[\w\d_-]+$").match(self['name']):
  79. self._errors.append('package name must be alphanumeric')
  80. elif not re.compile("[a-z\d_-]+").match(self['name']):
  81. self._warnings.append('package name should be in lower case')
  82. if self['version'].find('-') >= 0:
  83. self._errors.append('version field is not allowed to contain hyphens')
  84. if str(self['release']).find('-') >= 0:
  85. self._errors.append('release field is not allowed to contain hyphens')
  86. # Description isn't supposed to be longer than 80 characters
  87. if self['description'] and len(self['description']) > 80:
  88. self._warnings.append('description should not exceed 80 characters')
  89. # Make sure the number of sources and checksums is the same
  90. found_sums = False
  91. for checksum in ('md5sums', 'sha1sums', 'sha256sums', 'sha384sums',
  92. 'sha512sums'):
  93. if self[checksum]:
  94. found_sums = True
  95. if len(self[checksum]) != len(self['source']):
  96. self._errors.append('amount of %s and sources does not match'
  97. % checksum)
  98. if self['source'] and not found_sums:
  99. self._errors.append('sources exist without checksums')
  100. # Set some variables to quickly determine whether the package is valid
  101. self._validated = True
  102. if not self.has_errors():
  103. self._is_valid = True
  104. def is_valid(self):
  105. """If Package wasn't validated already, validate and report whether
  106. package is valid"""
  107. if not self._validated:
  108. self.validate()
  109. return self._is_valid
  110. def has_errors(self):
  111. """Determine whether package has any errors"""
  112. return len(self._errors) > 0
  113. def has_warnings(self):
  114. """Determin whether the package has any warnings"""
  115. return len(self._warnings) > 0
  116. def get_errors(self):
  117. """Retrieve a list of errors"""
  118. return self._errors
  119. def get_warnings(self):
  120. """Retrieve a list of warnings"""
  121. return self._warnings