| #!/usr/bin/env python3 |
| # Copyright (c) 2023 Project CHIP Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # This automates ZAP version changes as the zap version is repeated |
| # in many places |
| |
| import logging |
| import os |
| import re |
| import sys |
| from enum import Flag, auto |
| |
| import click |
| import coloredlogs |
| |
| # Supported log levels, mapping string values required for argument |
| # parsing into logging constants |
| __LOG_LEVELS__ = { |
| 'debug': logging.DEBUG, |
| 'info': logging.INFO, |
| 'warn': logging.WARN, |
| 'fatal': logging.FATAL, |
| } |
| |
| # A version is of the form: v2023.01.09-nightly |
| # |
| # At this time we hard-code nightly however we may need to figure out a more |
| # generic version string once we stop using nightly builds |
| ZAP_VERSION_RE = re.compile(r'v(\d\d\d\d)\.(\d\d)\.(\d\d)-nightly') |
| |
| # A list of files where ZAP is maintained. You can get a similar list using: |
| # |
| # rg v2023.01.09-nightly --hidden --no-ignore --files-with-matches |
| # |
| # Excluding THIS file and excluding anything in .environment (logs) |
| # |
| # Set as a separate list to not pay the price of a full grep as the list of |
| # files is not likely to change often |
| USAGE_FILES_DEPENDING_ON_ZAP_VERSION = [ |
| '.github/workflows/build.yaml', |
| '.github/workflows/darwin-tests.yaml', |
| '.github/workflows/darwin.yaml', |
| '.github/workflows/fuzzing-build.yaml', |
| '.github/workflows/tests.yaml', |
| 'integrations/docker/images/chip-cert-bins/Dockerfile', |
| ] |
| |
| # Note that chip-cert-bits is assumed USAGE on purpose (it compiles code) |
| # |
| # The chip-build image change will affect all other images as they extend |
| # chip-build |
| DOCKER_FILES_DEPENDING_ON_ZAP_VERSION = [ |
| 'integrations/docker/images/chip-build/Dockerfile', |
| ] |
| |
| |
| class UpdateChoice(Flag): |
| # Usage updates the CI, chip-cert and execution logic. Generally everything |
| # that would make use of the updated zap version |
| USAGE = auto() |
| |
| # Docker updates just the chip-build (and as a side-effect underlying) |
| # image(s). This is a pre-requisite to be able to start using the new |
| # version. |
| DOCKER = auto() |
| |
| |
| __UPDATE_CHOICES__ = { |
| 'docker': UpdateChoice.DOCKER, |
| 'usage': UpdateChoice.USAGE, |
| 'all': UpdateChoice.DOCKER | UpdateChoice.USAGE, |
| } |
| |
| # NOTE: you likely need to also update |
| # integrations/docker/images/chip-build/version |
| # |
| # in PRs that update chip-build Dockerfiles. This update is not automated in |
| # this script. |
| |
| # Apart from the above files which contain an exact ZAP version, the zap |
| # execution script contains the mimimal zap execution version, which generally |
| # we also enforce to be the current version. |
| # |
| # That line is of the form "MIN_ZAP_VERSION = '2021.1.9'" |
| ZAP_EXECUTION_SCRIPT = 'scripts/tools/zap/zap_execution.py' |
| ZAP_EXECUTION_MIN_RE = re.compile(r'(MIN_ZAP_VERSION = .)(\d\d\d\d\.\d\d?\.\d\d?)(.)') |
| |
| CHIP_ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) |
| |
| |
| @click.command() |
| @click.option( |
| '--log-level', |
| default='INFO', |
| type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), |
| help='Determines the verbosity of script output.') |
| @click.option( |
| '--update', |
| default='docker', |
| type=click.Choice(__UPDATE_CHOICES__.keys(), case_sensitive=False), |
| help='What to update: docker, usage, all. Default is "docker".') |
| @click.option( |
| '--new-version', |
| default=None, |
| help='What version of ZAP to update to (like "v2023.01.09-nightly". If not set, versions will just be printed.') |
| def version_update(log_level, update, new_version): |
| coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt='%(asctime)s %(levelname)-7s %(message)s') |
| |
| update = __UPDATE_CHOICES__[update] |
| |
| if new_version: |
| parsed = ZAP_VERSION_RE.match(new_version) |
| if not parsed: |
| logging.error(f"Version '{new_version}' does not seem to parse as a ZAP VERSION") |
| sys.exit(1) |
| |
| # get the numeric version for zap_execution |
| # |
| # This makes every group element (date section) to a base 10 integer, |
| # so for 'v2023.01.11-nightly' this gets (2023, 1, 11) |
| zap_min_version = tuple(map(lambda x: int(x, 10), parsed.groups())) |
| |
| files_to_update = [] |
| if UpdateChoice.USAGE in update: |
| files_to_update += USAGE_FILES_DEPENDING_ON_ZAP_VERSION |
| |
| if UpdateChoice.DOCKER in update: |
| files_to_update += DOCKER_FILES_DEPENDING_ON_ZAP_VERSION |
| |
| for name in files_to_update: |
| with open(os.path.join(CHIP_ROOT_DIR, name), 'rt') as f: |
| file_data = f.read() |
| |
| # Write out any matches. Note that we only write distinct matches as |
| # zap versions may occur several times in the same file |
| found_versions = set() |
| for m in ZAP_VERSION_RE.finditer(file_data): |
| version = file_data[m.start():m.end()] |
| if not version in found_versions: |
| logging.info('%s currently used in %s', version, name) |
| found_versions.add(version) |
| |
| # If we update, perform the update |
| if new_version: |
| search_pos = 0 |
| need_replace = False |
| m = ZAP_VERSION_RE.search(file_data, search_pos) |
| while m: |
| version = file_data[m.start():m.end()] |
| if version == new_version: |
| logging.warning("Nothing to replace. Version already %s", version) |
| break |
| file_data = file_data[:m.start()] + new_version + file_data[m.end():] |
| need_replace = True |
| search_pos = m.end() # generally ok since our versions are fixed length |
| m = ZAP_VERSION_RE.search(file_data, search_pos) |
| |
| if need_replace: |
| logging.info('Replacing with version %s in %s', new_version, name) |
| |
| with open(os.path.join(CHIP_ROOT_DIR, name), 'wt') as f: |
| f.write(file_data) |
| |
| # Finally, check zap_execution for any version update |
| if UpdateChoice.USAGE in update: |
| with open(os.path.join(CHIP_ROOT_DIR, ZAP_EXECUTION_SCRIPT), 'rt') as f: |
| file_data = f.read() |
| |
| m = ZAP_EXECUTION_MIN_RE.search(file_data) |
| logging.info("Min version %s in %s", m.group(2), ZAP_EXECUTION_SCRIPT) |
| if new_version: |
| new_min_version = ("%d.%d.%d" % zap_min_version) |
| file_data = file_data[:m.start()] + m.group(1) + new_min_version + m.group(3) + file_data[m.end():] |
| logging.info('Updating min version to %s in %s', new_min_version, ZAP_EXECUTION_SCRIPT) |
| |
| with open(os.path.join(CHIP_ROOT_DIR, ZAP_EXECUTION_SCRIPT), 'wt') as f: |
| f.write(file_data) |
| |
| |
| if __name__ == '__main__': |
| version_update() |