getdeveloperlib.py 6.4 KB

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