getdeveloperlib.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from __future__ import print_function
  2. import os
  3. import re
  4. import glob
  5. import subprocess
  6. import sys
  7. #
  8. # Patch parsing functions
  9. #
  10. FIND_INFRA_IN_PATCH = re.compile("^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$")
  11. def analyze_patch(patch):
  12. """Parse one patch and return the list of files modified, added or
  13. removed by the patch."""
  14. files = set()
  15. infras = set()
  16. for line in patch:
  17. # If the patch is adding a package, find which infra it is
  18. m = FIND_INFRA_IN_PATCH.match(line)
  19. if m:
  20. infras.add(m.group(2))
  21. if not line.startswith("+++ "):
  22. continue
  23. line.strip()
  24. fname = line[line.find("/") + 1:].strip()
  25. if fname == "dev/null":
  26. continue
  27. files.add(fname)
  28. return (files, infras)
  29. FIND_INFRA_IN_MK = re.compile("^\$\(eval \$\((host-)?([^-]*)-package\)\)$")
  30. def fname_get_package_infra(fname):
  31. """Checks whether the file name passed as argument is a Buildroot .mk
  32. file describing a package, and find the infrastructure it's using."""
  33. if not fname.endswith(".mk"):
  34. return None
  35. if not os.path.exists(fname):
  36. return None
  37. with open(fname, "r") as f:
  38. for line in f:
  39. line = line.strip()
  40. m = FIND_INFRA_IN_MK.match(line)
  41. if m:
  42. return m.group(2)
  43. return None
  44. def get_infras(files):
  45. """Search in the list of files for .mk files, and collect the package
  46. infrastructures used by those .mk files."""
  47. infras = set()
  48. for fname in files:
  49. infra = fname_get_package_infra(fname)
  50. if infra:
  51. infras.add(infra)
  52. return infras
  53. def analyze_patches(patches):
  54. """Parse a list of patches and returns the list of files modified,
  55. added or removed by the patches, as well as the list of package
  56. infrastructures used by those patches (if any)"""
  57. allfiles = set()
  58. allinfras = set()
  59. for patch in patches:
  60. (files, infras) = analyze_patch(patch)
  61. allfiles = allfiles | files
  62. allinfras = allinfras | infras
  63. allinfras = allinfras | get_infras(allfiles)
  64. return (allfiles, allinfras)
  65. #
  66. # DEVELOPERS file parsing functions
  67. #
  68. class Developer:
  69. def __init__(self, name, files):
  70. self.name = name
  71. self.files = files
  72. self.packages = parse_developer_packages(files)
  73. self.architectures = parse_developer_architectures(files)
  74. self.infras = parse_developer_infras(files)
  75. def hasfile(self, f):
  76. f = os.path.abspath(f)
  77. for fs in self.files:
  78. if f.startswith(fs):
  79. return True
  80. return False
  81. def __repr__(self):
  82. name = '\'' + self.name.split(' <')[0][:20] + '\''
  83. things = []
  84. if len(self.files):
  85. things.append('{} files'.format(len(self.files)))
  86. if len(self.packages):
  87. things.append('{} pkgs'.format(len(self.packages)))
  88. if len(self.architectures):
  89. things.append('{} archs'.format(len(self.architectures)))
  90. if len(self.infras):
  91. things.append('{} infras'.format(len(self.infras)))
  92. if things:
  93. return 'Developer <{} ({})>'.format(name, ', '.join(things))
  94. else:
  95. return 'Developer <' + name + '>'
  96. def parse_developer_packages(fnames):
  97. """Given a list of file patterns, travel through the Buildroot source
  98. tree to find which packages are implemented by those file
  99. patterns, and return a list of those packages."""
  100. packages = set()
  101. for fname in fnames:
  102. for root, dirs, files in os.walk(fname):
  103. for f in files:
  104. path = os.path.join(root, f)
  105. if fname_get_package_infra(path):
  106. pkg = os.path.splitext(f)[0]
  107. packages.add(pkg)
  108. return packages
  109. def parse_arches_from_config_in(fname):
  110. """Given a path to an arch/Config.in.* file, parse it to get the list
  111. of BR2_ARCH values for this architecture."""
  112. arches = set()
  113. with open(fname, "r") as f:
  114. parsing_arches = False
  115. for line in f:
  116. line = line.strip()
  117. if line == "config BR2_ARCH":
  118. parsing_arches = True
  119. continue
  120. if parsing_arches:
  121. m = re.match("^\s*default \"([^\"]*)\".*", line)
  122. if m:
  123. arches.add(m.group(1))
  124. else:
  125. parsing_arches = False
  126. return arches
  127. def parse_developer_architectures(fnames):
  128. """Given a list of file names, find the ones starting by
  129. 'arch/Config.in.', and use that to determine the architecture a
  130. developer is working on."""
  131. arches = set()
  132. for fname in fnames:
  133. if not re.match("^.*/arch/Config\.in\..*$", fname):
  134. continue
  135. arches = arches | parse_arches_from_config_in(fname)
  136. return arches
  137. def parse_developer_infras(fnames):
  138. infras = set()
  139. for fname in fnames:
  140. m = re.match("^package/pkg-([^.]*).mk$", fname)
  141. if m:
  142. infras.add(m.group(1))
  143. return infras
  144. def parse_developers(basepath=None):
  145. """Parse the DEVELOPERS file and return a list of Developer objects."""
  146. developers = []
  147. linen = 0
  148. if basepath is None:
  149. basepath = os.getcwd()
  150. with open(os.path.join(basepath, "DEVELOPERS"), "r") as f:
  151. files = []
  152. name = None
  153. for line in f:
  154. line = line.strip()
  155. if line.startswith("#"):
  156. continue
  157. elif line.startswith("N:"):
  158. if name is not None or len(files) != 0:
  159. print("Syntax error in DEVELOPERS file, line %d" % linen,
  160. file=sys.stderr)
  161. name = line[2:].strip()
  162. elif line.startswith("F:"):
  163. fname = line[2:].strip()
  164. dev_files = glob.glob(os.path.join(basepath, fname))
  165. if len(dev_files) == 0:
  166. print("WARNING: '%s' doesn't match any file" % fname,
  167. file=sys.stderr)
  168. files += dev_files
  169. elif line == "":
  170. if not name:
  171. continue
  172. developers.append(Developer(name, files))
  173. files = []
  174. name = None
  175. else:
  176. print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, line),
  177. file=sys.stderr)
  178. return None
  179. linen += 1
  180. # handle last developer
  181. if name is not None:
  182. developers.append(Developer(name, files))
  183. return developers
  184. def check_developers(developers, basepath=None):
  185. """Look at the list of files versioned in Buildroot, and returns the
  186. list of files that are not handled by any developer"""
  187. if basepath is None:
  188. basepath = os.getcwd()
  189. cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"]
  190. files = subprocess.check_output(cmd).strip().split("\n")
  191. unhandled_files = []
  192. for f in files:
  193. handled = False
  194. for d in developers:
  195. if d.hasfile(os.path.join(basepath, f)):
  196. handled = True
  197. break
  198. if not handled:
  199. unhandled_files.append(f)
  200. return unhandled_files