update_cluster_revisions.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2020 Project CHIP Authors
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. import argparse
  18. import json
  19. import logging
  20. import multiprocessing
  21. import os
  22. import subprocess
  23. import sys
  24. from pathlib import Path
  25. CHIP_ROOT_DIR = os.path.realpath(
  26. os.path.join(os.path.dirname(__file__), '../../..'))
  27. def getTargets():
  28. ROOTS_TO_SEARCH = [
  29. './examples',
  30. './src/controller/data_model',
  31. './scripts/tools/zap/tests/inputs',
  32. ]
  33. targets = []
  34. for root in ROOTS_TO_SEARCH:
  35. for filepath in Path(root).rglob('*.zap'):
  36. targets.append(filepath)
  37. return targets
  38. def checkPythonVersion():
  39. if sys.version_info[0] < 3:
  40. print('Must use Python 3. Current version is ' +
  41. str(sys.version_info[0]))
  42. exit(1)
  43. def runArgumentsParser():
  44. parser = argparse.ArgumentParser(
  45. description='Update the ClusterRevision for a chosen cluster in all .zap files')
  46. parser.add_argument('--cluster-id', default=None, action='store',
  47. help='The id of the cluster, as hex, for which the cluster revision should be updated.')
  48. parser.add_argument('--new-revision', default=None, action='store',
  49. help='The new cluster revision as a decimal integer')
  50. parser.add_argument('--old-revision', default=None, action='store',
  51. help='If set, only clusters with this old revision will be updated. This is a decimal integer.')
  52. parser.add_argument('--dry-run', default=False, action='store_true',
  53. help="Don't do any generation, just log what .zap files would be updated (default: False)")
  54. parser.add_argument('--parallel', action='store_true')
  55. parser.add_argument('--no-parallel', action='store_false', dest='parallel')
  56. parser.set_defaults(parallel=True)
  57. args = parser.parse_args()
  58. if args.cluster_id is None:
  59. logging.error("Must have a cluster id")
  60. sys.exit(1)
  61. if args.new_revision is None:
  62. logging.error("Must have a new cluster revision")
  63. sys.exit(1)
  64. args.cluster_id = int(args.cluster_id, 16)
  65. return args
  66. def isClusterRevisionAttribute(attribute):
  67. if attribute['mfgCode'] is not None:
  68. return False
  69. if attribute['code'] != 0xFFFD:
  70. return False
  71. if attribute['name'] != "ClusterRevision":
  72. logging.error("Attribute has ClusterRevision id but wrong name")
  73. return False
  74. return True
  75. def updateOne(item):
  76. """
  77. Helper method that may be run in parallel to update a single target.
  78. """
  79. (args, target) = item
  80. with open(target, "r") as file:
  81. data = json.load(file)
  82. for endpointType in data['endpointTypes']:
  83. for cluster in endpointType['clusters']:
  84. if cluster['mfgCode'] is None and cluster['code'] == args.cluster_id:
  85. for attribute in cluster['attributes']:
  86. if isClusterRevisionAttribute(attribute):
  87. if args.old_revision is None or attribute['defaultValue'] == args.old_revision:
  88. attribute['defaultValue'] = args.new_revision
  89. with open(target, "w") as file:
  90. json.dump(data, file)
  91. # Now run convert.py on the file to have ZAP reformat it however it likes.
  92. subprocess.check_call(['./scripts/tools/zap/convert.py', target])
  93. def main():
  94. checkPythonVersion()
  95. logging.basicConfig(
  96. level=logging.INFO,
  97. format='%(asctime)s %(name)s %(levelname)-7s %(message)s'
  98. )
  99. args = runArgumentsParser()
  100. os.chdir(CHIP_ROOT_DIR)
  101. targets = getTargets()
  102. if args.dry_run:
  103. for target in targets:
  104. print(f"Will try to update: {target}")
  105. sys.exit(0)
  106. items = [(args, target) for target in targets]
  107. if args.parallel:
  108. # Ensure each zap run is independent
  109. os.environ['ZAP_TEMPSTATE'] = '1'
  110. with multiprocessing.Pool() as pool:
  111. for _ in pool.imap_unordered(updateOne, items):
  112. pass
  113. else:
  114. for item in items:
  115. updateOne(item)
  116. if __name__ == '__main__':
  117. main()