b4-wrapper-poky.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright OpenEmbedded Contributors
  4. #
  5. # SPDX-License-Identifier: MIT
  6. #
  7. # This script is to be called by b4:
  8. # - through the b4.prep-perpatch-check-cmd with "prep-perpatch-check-cmd" as
  9. # first argument,
  10. # - through b4.send-auto-cc-cmd with "send-auto-cc-cmd" as first argument,
  11. # - through b4.send-auto-to-cmd with "send-auto-to-cmd" as first argument,
  12. #
  13. # When prep-perpatch-check-cmd is passsed:
  14. #
  15. # This checks that a patch makes changes to at most one project in the poky
  16. # combo repo (that is, out of yocto-docs, bitbake, openembedded-core combined
  17. # into poky and the poky-specific files).
  18. #
  19. # Printing something to stdout in this file will result in b4 prep --check fail
  20. # for the currently parsed patch.
  21. #
  22. # It checks that all patches in the series make changes to at most one project.
  23. #
  24. # When send-auto-cc-cmd is passed:
  25. #
  26. # This returns the list of Cc recipients for a patch.
  27. #
  28. # When send-auto-to-cmd is passed:
  29. #
  30. # This returns the list of To recipients for a patch.
  31. #
  32. # This script takes as stdin a patch.
  33. import pathlib
  34. import re
  35. import shutil
  36. import subprocess
  37. import sys
  38. cmd = sys.argv[1]
  39. patch = sys.stdin.readlines()
  40. # Subject field is used to identify the last patch as this script is called for
  41. # each patch. We edit the same file in a series by using the References field
  42. # unique identifier to check which projects are modified by earlier patches in
  43. # the series. To avoid cluttering the disk, the last patch in the list removes
  44. # that shared file.
  45. re_subject = re.compile(r'^Subject:.*\[.*PATCH.*\s(\d+)/\1')
  46. re_ref = re.compile(r'^References: <(.*)>$')
  47. subject = None
  48. ref = None
  49. if not shutil.which("lsdiff"):
  50. print("lsdiff missing from host, please install patchutils", file=sys.stderr)
  51. sys.exit(-1)
  52. try:
  53. one_patch_series = False
  54. for line in patch:
  55. subject = re_subject.match(line)
  56. if subject:
  57. # Handle [PATCH 1/1]
  58. if subject.group(1) == 1:
  59. one_patch_series = True
  60. break
  61. if re.match(r'^Subject: .*\[.*PATCH[^/]*\]', line):
  62. # Single patch is named [PATCH] but if there are prefix, it could be
  63. # [PATCH prefix], so handle everything that doesn't have a /
  64. # character which is used as separator between current patch number
  65. # and total patch number
  66. one_patch_series = True
  67. break
  68. if cmd == "prep-perpatch-check-cmd" and not one_patch_series:
  69. for line in patch:
  70. ref = re_ref.match(line)
  71. if ref:
  72. break
  73. if not ref:
  74. print("Failed to find ref to cover letter (References:)...", file=sys.stderr)
  75. sys.exit(-2)
  76. ref = ref.group(1)
  77. series_check = pathlib.Path(f".tmp-{ref}")
  78. patch = "".join(patch)
  79. if cmd == "send-auto-cc-cmd":
  80. # Patches to BitBake documentation should also go to yocto-docs mailing list
  81. project_paths = {
  82. "yocto-docs": ["bitbake/doc/*"],
  83. }
  84. else:
  85. project_paths = {
  86. "bitbake": ["bitbake/*"],
  87. "yocto-docs": ["documentation/*"],
  88. "poky": [
  89. "meta-poky/*",
  90. "meta-yocto-bsp/*",
  91. "README.hardware.md",
  92. "README.poky.md",
  93. # scripts/b4-wrapper-poky.py is only run by b4 when in poky
  94. # git repo. With that limitation, changes made to .b4-config
  95. # can only be for poky's and not OE-Core's as only poky's is
  96. # stored in poky git repo.
  97. ".b4-config",
  98. ],
  99. }
  100. # List of projects touched by this patch
  101. projs = []
  102. # Any file not matched by any path in project_paths means it is from
  103. # OE-Core.
  104. # When matching some path in project_paths, remove the matched files from
  105. # that list.
  106. files_left = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"],
  107. input=patch, text=True)
  108. files_left = set(files_left)
  109. for proj, proj_paths in project_paths.items():
  110. lsdiff_args = [f"--include={path}" for path in proj_paths]
  111. files = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"] + lsdiff_args,
  112. input=patch, text=True)
  113. if len(files):
  114. files_left = files_left - set(files)
  115. projs.append(proj)
  116. continue
  117. # Handle patches made with --no-prefix
  118. files = subprocess.check_output(["lsdiff"] + lsdiff_args,
  119. input=patch, text=True)
  120. if len(files):
  121. files_left = files_left - set(files)
  122. projs.append(proj)
  123. # Catch-all for everything not poky-specific or in bitbake/yocto-docs
  124. if len(files_left) and cmd != "send-auto-cc-cmd":
  125. projs.append("openembedded-core")
  126. if cmd == "prep-perpatch-check-cmd":
  127. if len(projs) > 1:
  128. print(f"Diff spans more than one project ({', '.join(sorted(projs))}), split into multiple commits...",
  129. file=sys.stderr)
  130. sys.exit(-3)
  131. # No need to check other patches in the series as there aren't any
  132. if one_patch_series:
  133. sys.exit(0)
  134. # This should be replaced once b4 supports prep-perseries-check-cmd (or something similar)
  135. if series_check.exists():
  136. # NOT race-free if b4 decides to parallelize prep-perpatch-check-cmd
  137. series_projs = series_check.read_text().split('\n')
  138. else:
  139. series_projs = []
  140. series_projs += projs
  141. uniq_series_projs = set(series_projs)
  142. # NOT race-free, if b4 decides to parallelize prep-perpatch-check-cmd
  143. series_check.write_text('\n'.join(uniq_series_projs))
  144. if len(uniq_series_projs) > 1:
  145. print(f"Series spans more than one project ({', '.join(sorted(uniq_series_projs))}), split into multiple series...",
  146. file=sys.stderr)
  147. sys.exit(-4)
  148. else: # send-auto-cc-cmd / send-auto-to-cmd
  149. ml_projs = {
  150. "bitbake": "bitbake-devel@lists.openembedded.org",
  151. "yocto-docs": "docs@lists.yoctoproject.org",
  152. "poky": "poky@lists.yoctoproject.org",
  153. "openembedded-core": "openembedded-core@lists.openembedded.org",
  154. }
  155. print("\n".join([ml_projs[ml] for ml in projs]))
  156. sys.exit(0)
  157. finally:
  158. # Last patch in the series, cleanup tmp file
  159. if subject and ref and series_check.exists():
  160. series_check.unlink()