blob: 152db957d492dcdfbfc9698bd06399dbef86836c [file] [log] [blame]
#!/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()