version_update.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2023 Project CHIP Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # This automates ZAP version changes as the zap version is repeated
  16. # in many places
  17. import logging
  18. import os
  19. import re
  20. import sys
  21. from enum import Flag, auto
  22. import click
  23. import coloredlogs
  24. # Supported log levels, mapping string values required for argument
  25. # parsing into logging constants
  26. __LOG_LEVELS__ = {
  27. 'debug': logging.DEBUG,
  28. 'info': logging.INFO,
  29. 'warn': logging.WARN,
  30. 'fatal': logging.FATAL,
  31. }
  32. # A version is of the form: v2023.01.09-nightly
  33. #
  34. # At this time we hard-code nightly however we may need to figure out a more
  35. # generic version string once we stop using nightly builds
  36. ZAP_VERSION_RE = re.compile(r'v(\d\d\d\d)\.(\d\d)\.(\d\d)-nightly')
  37. # A list of files where ZAP is maintained. You can get a similar list using:
  38. #
  39. # rg v2023.01.09-nightly --hidden --no-ignore --files-with-matches
  40. #
  41. # Excluding THIS file and excluding anything in .environment (logs)
  42. #
  43. # Set as a separate list to not pay the price of a full grep as the list of
  44. # files is not likely to change often
  45. USAGE_FILES_DEPENDING_ON_ZAP_VERSION = [
  46. 'integrations/docker/images/chip-cert-bins/Dockerfile',
  47. 'scripts/setup/zap.json',
  48. 'scripts/setup/zap.version',
  49. ]
  50. class UpdateChoice(Flag):
  51. # Usage updates the CI, chip-cert and execution logic. Generally everything
  52. # that would make use of the updated zap version
  53. USAGE = auto()
  54. __UPDATE_CHOICES__ = {
  55. 'usage': UpdateChoice.USAGE,
  56. }
  57. # Apart from the above files which contain an exact ZAP version, the zap
  58. # execution script contains the mimimal zap execution version, which generally
  59. # we also enforce to be the current version.
  60. #
  61. # That line is of the form "MIN_ZAP_VERSION = '2021.1.9'"
  62. ZAP_EXECUTION_SCRIPT = 'scripts/tools/zap/zap_execution.py'
  63. ZAP_EXECUTION_MIN_RE = re.compile(
  64. r'(MIN_ZAP_VERSION = .)(\d\d\d\d\.\d\d?\.\d\d?)(.)')
  65. CHIP_ROOT_DIR = os.path.abspath(os.path.join(
  66. os.path.dirname(__file__), '..', '..', '..'))
  67. @click.command()
  68. @click.option(
  69. '--log-level',
  70. default='INFO',
  71. type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False),
  72. help='Determines the verbosity of script output.')
  73. @click.option(
  74. '--update',
  75. default='usage',
  76. type=click.Choice(__UPDATE_CHOICES__.keys(), case_sensitive=False),
  77. help='What to update: usage (only choice currently).')
  78. @click.option(
  79. '--new-version',
  80. default=None,
  81. help='What version of ZAP to update to (like "v2023.01.09-nightly". If not set, versions will just be printed.')
  82. def version_update(log_level, update, new_version):
  83. coloredlogs.install(level=__LOG_LEVELS__[
  84. log_level], fmt='%(asctime)s %(levelname)-7s %(message)s')
  85. update = __UPDATE_CHOICES__[update]
  86. if new_version:
  87. parsed = ZAP_VERSION_RE.match(new_version)
  88. if not parsed:
  89. logging.error(
  90. f"Version '{new_version}' does not seem to parse as a ZAP VERSION")
  91. sys.exit(1)
  92. # get the numeric version for zap_execution
  93. #
  94. # This makes every group element (date section) to a base 10 integer,
  95. # so for 'v2023.01.11-nightly' this gets (2023, 1, 11)
  96. zap_min_version = tuple(map(lambda x: int(x, 10), parsed.groups()))
  97. files_to_update = []
  98. if UpdateChoice.USAGE in update:
  99. files_to_update += USAGE_FILES_DEPENDING_ON_ZAP_VERSION
  100. for name in files_to_update:
  101. with open(os.path.join(CHIP_ROOT_DIR, name), 'rt') as f:
  102. file_data = f.read()
  103. # Write out any matches. Note that we only write distinct matches as
  104. # zap versions may occur several times in the same file
  105. found_versions = set()
  106. for m in ZAP_VERSION_RE.finditer(file_data):
  107. version = file_data[m.start():m.end()]
  108. if version not in found_versions:
  109. logging.info('%s currently used in %s', version, name)
  110. found_versions.add(version)
  111. # If we update, perform the update
  112. if new_version:
  113. search_pos = 0
  114. need_replace = False
  115. m = ZAP_VERSION_RE.search(file_data, search_pos)
  116. while m:
  117. version = file_data[m.start():m.end()]
  118. if version == new_version:
  119. logging.warning(
  120. "Nothing to replace. Version already %s", version)
  121. break
  122. file_data = file_data[:m.start()] + \
  123. new_version + file_data[m.end():]
  124. need_replace = True
  125. search_pos = m.end() # generally ok since our versions are fixed length
  126. m = ZAP_VERSION_RE.search(file_data, search_pos)
  127. if need_replace:
  128. logging.info('Replacing with version %s in %s',
  129. new_version, name)
  130. with open(os.path.join(CHIP_ROOT_DIR, name), 'wt') as f:
  131. f.write(file_data)
  132. # Finally, check zap_execution for any version update
  133. if UpdateChoice.USAGE in update:
  134. with open(os.path.join(CHIP_ROOT_DIR, ZAP_EXECUTION_SCRIPT), 'rt') as f:
  135. file_data = f.read()
  136. m = ZAP_EXECUTION_MIN_RE.search(file_data)
  137. logging.info("Min version %s in %s", m.group(2), ZAP_EXECUTION_SCRIPT)
  138. if new_version:
  139. new_min_version = ("%d.%d.%d" % zap_min_version)
  140. file_data = file_data[:m.start()] + m.group(1) + \
  141. new_min_version + m.group(3) + file_data[m.end():]
  142. logging.info('Updating min version to %s in %s',
  143. new_min_version, ZAP_EXECUTION_SCRIPT)
  144. with open(os.path.join(CHIP_ROOT_DIR, ZAP_EXECUTION_SCRIPT), 'wt') as f:
  145. f.write(file_data)
  146. if __name__ == '__main__':
  147. version_update()