checkout_submodules.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2022 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 configparser
  19. import logging
  20. import os
  21. import subprocess
  22. from collections import namedtuple
  23. CHIP_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
  24. ALL_PLATFORMS = set([
  25. 'ameba',
  26. 'android',
  27. 'asr',
  28. 'bl602',
  29. 'bouffalolab',
  30. 'cc13xx_26xx',
  31. 'cc32xx',
  32. 'darwin',
  33. 'efr32',
  34. 'esp32',
  35. 'infineon',
  36. 'k32w0',
  37. 'linux',
  38. 'mbed',
  39. 'nrfconnect',
  40. 'qpg',
  41. 'stm32',
  42. 'telink',
  43. 'tizen',
  44. 'webos',
  45. 'mw320',
  46. 'genio',
  47. 'openiotsdk',
  48. 'silabs_docker',
  49. ])
  50. Module = namedtuple('Module', 'name path platforms')
  51. def load_module_info() -> None:
  52. config = configparser.ConfigParser()
  53. config.read(os.path.join(CHIP_ROOT, '.gitmodules'))
  54. for name, module in config.items():
  55. if name != 'DEFAULT':
  56. platforms = module.get('platforms', '').split(',')
  57. platforms = set(filter(None, platforms))
  58. assert not (platforms - ALL_PLATFORMS), "Submodule's platform not contained in ALL_PLATFORMS"
  59. name = name.replace('submodule "', '').replace('"', '')
  60. yield Module(name=name, path=module['path'], platforms=platforms)
  61. def module_matches_platforms(module: Module, platforms: set) -> bool:
  62. # If the module is not associated with any specific platform, treat it as a match.
  63. if not module.platforms:
  64. return True
  65. return bool(platforms & module.platforms)
  66. def module_initialized(module: Module) -> bool:
  67. return bool(os.listdir(os.path.join(CHIP_ROOT, module.path)))
  68. def make_chip_root_safe_directory() -> None:
  69. # Can't use check_output, git will exit(1) if the setting has no existing value
  70. config = subprocess.run(['git', 'config', '--global', '--null', '--get-all',
  71. 'safe.directory'], stdout=subprocess.PIPE, text=True)
  72. existing = []
  73. if config.returncode != 1:
  74. config.check_returncode()
  75. existing = config.stdout.split('\0')
  76. if CHIP_ROOT not in existing:
  77. logging.info("Adding CHIP_ROOT to global git safe.directory configuration")
  78. subprocess.check_call(['git', 'config', '--global', '--add', 'safe.directory', CHIP_ROOT])
  79. def checkout_modules(modules: list, shallow: bool, force: bool, recursive: bool) -> None:
  80. names = ', '.join([module.name for module in modules])
  81. logging.info(f'Checking out: {names}')
  82. cmd = ['git', '-C', CHIP_ROOT, 'submodule', '--quiet', 'update', '--init']
  83. cmd += ['--depth', '1'] if shallow else []
  84. cmd += ['--force'] if force else []
  85. cmd += ['--recursive'] if recursive else []
  86. cmd += [module.path for module in modules]
  87. subprocess.check_call(cmd)
  88. def deinit_modules(modules: list, force: bool) -> None:
  89. names = ', '.join([module.name for module in modules])
  90. logging.info(f'Deinitializing: {names}')
  91. cmd = ['git', '-C', CHIP_ROOT, 'submodule', '--quiet', 'deinit']
  92. cmd += ['--force'] if force else []
  93. cmd += [module.path for module in modules]
  94. subprocess.check_call(cmd)
  95. def main():
  96. logging.basicConfig(format='%(message)s', level=logging.INFO)
  97. parser = argparse.ArgumentParser(description='Checkout or update relevant git submodules')
  98. parser.add_argument('--allow-changing-global-git-config', action='store_true',
  99. help='Allow global git options to be modified if necessary, e.g. safe.directory')
  100. parser.add_argument('--shallow', action='store_true', help='Fetch submodules without history')
  101. parser.add_argument('--platform', nargs='+', choices=ALL_PLATFORMS, default=[],
  102. help='Process submodules for specific platforms only')
  103. parser.add_argument('--force', action='store_true', help='Perform action despite of warnings')
  104. parser.add_argument('--deinit-unmatched', action='store_true',
  105. help='Deinitialize submodules for non-matching platforms')
  106. parser.add_argument('--recursive', action='store_true', help='Recursive init of the listed submodules')
  107. args = parser.parse_args()
  108. modules = list(load_module_info())
  109. selected_platforms = set(args.platform)
  110. selected_modules = [m for m in modules if module_matches_platforms(m, selected_platforms)]
  111. unmatched_modules = [m for m in modules if not module_matches_platforms(
  112. m, selected_platforms) and module_initialized(m)]
  113. if args.allow_changing_global_git_config:
  114. make_chip_root_safe_directory() # ignore directory ownership issues for sub-modules
  115. checkout_modules(selected_modules, args.shallow, args.force, args.recursive)
  116. if args.deinit_unmatched and unmatched_modules:
  117. deinit_modules(unmatched_modules, args.force)
  118. if __name__ == '__main__':
  119. main()