2
1

getdeveloperlib.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. from __future__ import print_function
  2. import os
  3. import re
  4. import glob
  5. import subprocess
  6. import sys
  7. import unittest
  8. brpath = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
  9. #
  10. # Patch parsing functions
  11. #
  12. FIND_INFRA_IN_PATCH = re.compile(r"^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$")
  13. def analyze_patch(patch):
  14. """Parse one patch and return the list of files modified, added or
  15. removed by the patch."""
  16. files = set()
  17. infras = set()
  18. for line in patch:
  19. # If the patch is adding a package, find which infra it is
  20. m = FIND_INFRA_IN_PATCH.match(line)
  21. if m:
  22. infras.add(m.group(2))
  23. if not line.startswith("+++ "):
  24. continue
  25. line.strip()
  26. fname = line[line.find("/") + 1:].strip()
  27. if fname == "dev/null":
  28. continue
  29. files.add(fname)
  30. return (files, infras)
  31. FIND_INFRA_IN_MK = re.compile(r"^\$\(eval \$\((host-)?([^-]*)-package\)\)$")
  32. def fname_get_package_infra(fname):
  33. """Checks whether the file name passed as argument is a Buildroot .mk
  34. file describing a package, and find the infrastructure it's using."""
  35. if not fname.endswith(".mk"):
  36. return None
  37. if not os.path.exists(fname):
  38. return None
  39. with open(fname, "r") as f:
  40. for line in f:
  41. line = line.strip()
  42. m = FIND_INFRA_IN_MK.match(line)
  43. if m:
  44. return m.group(2)
  45. return None
  46. def get_infras(files):
  47. """Search in the list of files for .mk files, and collect the package
  48. infrastructures used by those .mk files."""
  49. infras = set()
  50. for fname in files:
  51. infra = fname_get_package_infra(fname)
  52. if infra:
  53. infras.add(infra)
  54. return infras
  55. def analyze_patches(patches):
  56. """Parse a list of patches and returns the list of files modified,
  57. added or removed by the patches, as well as the list of package
  58. infrastructures used by those patches (if any)"""
  59. allfiles = set()
  60. allinfras = set()
  61. for patch in patches:
  62. (files, infras) = analyze_patch(patch)
  63. allfiles = allfiles | files
  64. allinfras = allinfras | infras
  65. allinfras = allinfras | get_infras(allfiles)
  66. return (allfiles, allinfras)
  67. #
  68. # Unit-test parsing functions
  69. #
  70. def get_all_test_cases(suite):
  71. """Generate all test-cases from a given test-suite.
  72. :return: (test.module, test.name)"""
  73. if issubclass(type(suite), unittest.TestSuite):
  74. for test in suite:
  75. for res in get_all_test_cases(test):
  76. yield res
  77. else:
  78. yield (suite.__module__, suite.__class__.__name__)
  79. def list_unittests():
  80. """Use the unittest module to retreive all test cases from a given
  81. directory"""
  82. loader = unittest.TestLoader()
  83. suite = loader.discover(os.path.join(brpath, "support", "testing"))
  84. tests = {}
  85. for module, test in get_all_test_cases(suite):
  86. module_path = os.path.join("support", "testing", *module.split('.'))
  87. tests.setdefault(module_path, []).append('%s.%s' % (module, test))
  88. return tests
  89. unittests = {}
  90. #
  91. # DEVELOPERS file parsing functions
  92. #
  93. class Developer:
  94. def __init__(self, name, files):
  95. self.name = name
  96. self.files = files
  97. self.packages = parse_developer_packages(files)
  98. self.architectures = parse_developer_architectures(files)
  99. self.infras = parse_developer_infras(files)
  100. self.runtime_tests = parse_developer_runtime_tests(files)
  101. self.defconfigs = parse_developer_defconfigs(files)
  102. def hasfile(self, f):
  103. for fs in self.files:
  104. if f.startswith(fs):
  105. return True
  106. return False
  107. def __repr__(self):
  108. name = '\'' + self.name.split(' <')[0][:20] + '\''
  109. things = []
  110. if len(self.files):
  111. things.append('{} files'.format(len(self.files)))
  112. if len(self.packages):
  113. things.append('{} pkgs'.format(len(self.packages)))
  114. if len(self.architectures):
  115. things.append('{} archs'.format(len(self.architectures)))
  116. if len(self.infras):
  117. things.append('{} infras'.format(len(self.infras)))
  118. if len(self.runtime_tests):
  119. things.append('{} tests'.format(len(self.runtime_tests)))
  120. if len(self.defconfigs):
  121. things.append('{} defconfigs'.format(len(self.defconfigs)))
  122. if things:
  123. return 'Developer <{} ({})>'.format(name, ', '.join(things))
  124. else:
  125. return 'Developer <' + name + '>'
  126. def parse_developer_packages(fnames):
  127. """Given a list of file patterns, travel through the Buildroot source
  128. tree to find which packages are implemented by those file
  129. patterns, and return a list of those packages."""
  130. packages = set()
  131. for fname in fnames:
  132. for root, dirs, files in os.walk(os.path.join(brpath, fname)):
  133. for f in files:
  134. path = os.path.join(root, f)
  135. if fname_get_package_infra(path):
  136. pkg = os.path.splitext(f)[0]
  137. packages.add(pkg)
  138. return packages
  139. def parse_arches_from_config_in(fname):
  140. """Given a path to an arch/Config.in.* file, parse it to get the list
  141. of BR2_ARCH values for this architecture."""
  142. arches = set()
  143. with open(fname, "r") as f:
  144. parsing_arches = False
  145. for line in f:
  146. line = line.strip()
  147. if line == "config BR2_ARCH":
  148. parsing_arches = True
  149. continue
  150. if parsing_arches:
  151. m = re.match(r"^\s*default \"([^\"]*)\".*", line)
  152. if m:
  153. arches.add(m.group(1))
  154. else:
  155. parsing_arches = False
  156. return arches
  157. def parse_developer_architectures(fnames):
  158. """Given a list of file names, find the ones starting by
  159. 'arch/Config.in.', and use that to determine the architecture a
  160. developer is working on."""
  161. arches = set()
  162. for fname in fnames:
  163. if not re.match(r"^.*/arch/Config\.in\..*$", fname):
  164. continue
  165. arches = arches | parse_arches_from_config_in(fname)
  166. return arches
  167. def parse_developer_infras(fnames):
  168. infras = set()
  169. for fname in fnames:
  170. m = re.match(r"^package/pkg-([^.]*).mk$", fname)
  171. if m:
  172. infras.add(m.group(1))
  173. return infras
  174. def parse_developer_defconfigs(fnames):
  175. """Given a list of file names, returns the config names
  176. corresponding to defconfigs."""
  177. return {os.path.basename(fname[:-10])
  178. for fname in fnames
  179. if fname.endswith('_defconfig')}
  180. def parse_developer_runtime_tests(fnames):
  181. """Given a list of file names, returns the runtime tests
  182. corresponding to the file."""
  183. all_files = []
  184. # List all files recursively
  185. for fname in fnames:
  186. if os.path.isdir(fname):
  187. for root, _dirs, files in os.walk(os.path.join(brpath, fname)):
  188. all_files += [os.path.join(root, f) for f in files]
  189. else:
  190. all_files.append(fname)
  191. # Get all runtime tests
  192. runtimes = set()
  193. for f in all_files:
  194. name = os.path.splitext(f)[0]
  195. if name in unittests:
  196. runtimes |= set(unittests[name])
  197. return runtimes
  198. def parse_developers():
  199. """Parse the DEVELOPERS file and return a list of Developer objects."""
  200. developers = []
  201. linen = 0
  202. global unittests
  203. unittests = list_unittests()
  204. with open(os.path.join(brpath, "DEVELOPERS"), "r") as f:
  205. files = []
  206. name = None
  207. for line in f:
  208. line = line.strip()
  209. if line.startswith("#"):
  210. continue
  211. elif line.startswith("N:"):
  212. if name is not None or len(files) != 0:
  213. print("Syntax error in DEVELOPERS file, line %d" % linen,
  214. file=sys.stderr)
  215. name = line[2:].strip()
  216. elif line.startswith("F:"):
  217. fname = line[2:].strip()
  218. dev_files = glob.glob(os.path.join(brpath, fname))
  219. if len(dev_files) == 0:
  220. print("WARNING: '%s' doesn't match any file" % fname,
  221. file=sys.stderr)
  222. files += [os.path.relpath(f, brpath) for f in dev_files]
  223. elif line == "":
  224. if not name:
  225. continue
  226. developers.append(Developer(name, files))
  227. files = []
  228. name = None
  229. else:
  230. print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, line),
  231. file=sys.stderr)
  232. return None
  233. linen += 1
  234. # handle last developer
  235. if name is not None:
  236. developers.append(Developer(name, files))
  237. return developers
  238. def check_developers(developers, basepath=None):
  239. """Look at the list of files versioned in Buildroot, and returns the
  240. list of files that are not handled by any developer"""
  241. if basepath is None:
  242. basepath = os.getcwd()
  243. cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"]
  244. files = subprocess.check_output(cmd).strip().split("\n")
  245. unhandled_files = []
  246. for f in files:
  247. handled = False
  248. for d in developers:
  249. if d.hasfile(os.path.join(basepath, f)):
  250. handled = True
  251. break
  252. if not handled:
  253. unhandled_files.append(f)
  254. return unhandled_files