From 125413664fd1499ee5bf79fe4684fa47cb135109 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 12 May 2023 10:00:23 -0400 Subject: [PATCH] Add a script to mass-update cluster revisions in our .zap files. (#26514) We're going to need to do this for several clusters, and doing it by hand is really painful. Also fixes a bug in zap_convert_all.py when run in non-parallel mode. --- scripts/tools/zap/update_cluster_revisions.py | 154 ++++++++++++++++++ scripts/tools/zap_convert_all.py | 2 +- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100755 scripts/tools/zap/update_cluster_revisions.py diff --git a/scripts/tools/zap/update_cluster_revisions.py b/scripts/tools/zap/update_cluster_revisions.py new file mode 100755 index 00000000000000..e29a274cf47087 --- /dev/null +++ b/scripts/tools/zap/update_cluster_revisions.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020 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. +# + +import argparse +import json +import logging +import multiprocessing +import os +import subprocess +import sys +from pathlib import Path + +CHIP_ROOT_DIR = os.path.realpath( + os.path.join(os.path.dirname(__file__), '../../..')) + + +def getTargets(): + ROOTS_TO_SEARCH = [ + './examples', + './src/controller/data_model', + './scripts/tools/zap/tests/inputs', + ] + + targets = [] + for root in ROOTS_TO_SEARCH: + for filepath in Path(root).rglob('*.zap'): + targets.append(filepath) + + return targets + + +def checkPythonVersion(): + if sys.version_info[0] < 3: + print('Must use Python 3. Current version is ' + + str(sys.version_info[0])) + exit(1) + + +def runArgumentsParser(): + parser = argparse.ArgumentParser( + description='Update the ClusterRevision for a chosen cluster in all .zap files') + parser.add_argument('--cluster-id', default=None, action='store', + help='The id of the cluster, as hex, for which the cluster revision should be updated.') + parser.add_argument('--new-revision', default=None, action='store', + help='The new cluster revision as a decimal integer') + parser.add_argument('--old-revision', default=None, action='store', + help='If set, only clusters with this old revision will be updated. This is a decimal integer.') + parser.add_argument('--dry-run', default=False, action='store_true', + help="Don't do any generation, just log what .zap files would be updated (default: False)") + parser.add_argument('--parallel', action='store_true') + parser.add_argument('--no-parallel', action='store_false', dest='parallel') + parser.set_defaults(parallel=True) + + args = parser.parse_args() + + if args.cluster_id is None: + logging.error("Must have a cluster id") + sys.exit(1) + + if args.new_revision is None: + logging.error("Must have a new cluster revision") + sys.exit(1) + + args.cluster_id = int(args.cluster_id, 16) + + return args + + +def isClusterRevisionAttribute(attribute): + if attribute['mfgCode'] is not None: + return False + + if attribute['code'] != 0xFFFD: + return False + + if attribute['name'] != "ClusterRevision": + logging.error("Attribute has ClusterRevision id but wrong name") + return False + + return True + + +def updateOne(item): + """ + Helper method that may be run in parallel to update a single target. + """ + (args, target) = item + + with open(target, "r") as file: + data = json.load(file) + + for endpointType in data['endpointTypes']: + for cluster in endpointType['clusters']: + if cluster['mfgCode'] is None and cluster['code'] == args.cluster_id: + for attribute in cluster['attributes']: + if isClusterRevisionAttribute(attribute): + if args.old_revision is None or attribute['defaultValue'] == args.old_revision: + attribute['defaultValue'] = args.new_revision + + with open(target, "w") as file: + json.dump(data, file) + + # Now run convert.py on the file to have ZAP reformat it however it likes. + subprocess.check_call(['./scripts/tools/zap/convert.py', target]) + + +def main(): + checkPythonVersion() + + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(name)s %(levelname)-7s %(message)s' + ) + + args = runArgumentsParser() + + os.chdir(CHIP_ROOT_DIR) + + targets = getTargets() + + if args.dry_run: + for target in targets: + print(f"Will try to update: {target}") + sys.exit(0) + + items = [(args, target) for target in targets] + + if args.parallel: + # Ensure each zap run is independent + os.environ['ZAP_TEMPSTATE'] = '1' + with multiprocessing.Pool() as pool: + for _ in pool.imap_unordered(updateOne, items): + pass + else: + for item in items: + updateOne(item) + + +if __name__ == '__main__': + main() diff --git a/scripts/tools/zap_convert_all.py b/scripts/tools/zap_convert_all.py index 677067c93dc123..ba456ca1efeb90 100755 --- a/scripts/tools/zap_convert_all.py +++ b/scripts/tools/zap_convert_all.py @@ -105,7 +105,7 @@ def main(): pass else: for target in targets: - generateOne(target) + convertOne(target) if __name__ == '__main__':