ci_build_apps.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. """
  4. This file is used in CI generate binary files for different kinds of apps
  5. """
  6. import argparse
  7. import os
  8. import sys
  9. from collections import defaultdict
  10. from pathlib import Path
  11. from typing import List, Set
  12. from idf_build_apps import LOGGER, App, build_apps, find_apps, setup_logging
  13. from idf_ci_utils import IDF_PATH, get_pytest_app_paths, get_pytest_cases, get_ttfw_app_paths
  14. def get_pytest_apps(
  15. paths: List[str],
  16. target: str,
  17. config_rules_str: List[str],
  18. marker_expr: str,
  19. preserve_all: bool = False,
  20. ) -> List[App]:
  21. pytest_cases = get_pytest_cases(paths, target, marker_expr)
  22. _paths: Set[str] = set()
  23. test_related_app_configs = defaultdict(set)
  24. for case in pytest_cases:
  25. for app in case.apps:
  26. _paths.add(app.path)
  27. if os.getenv('INCLUDE_NIGHTLY_RUN') == '1':
  28. test_related_app_configs[app.path].add(app.config)
  29. elif os.getenv('NIGHTLY_RUN') == '1':
  30. if case.nightly_run:
  31. test_related_app_configs[app.path].add(app.config)
  32. else:
  33. if not case.nightly_run:
  34. test_related_app_configs[app.path].add(app.config)
  35. app_dirs = list(_paths)
  36. if not app_dirs:
  37. raise RuntimeError('No apps found')
  38. LOGGER.info(f'Found {len(app_dirs)} apps')
  39. app_dirs.sort()
  40. apps = find_apps(
  41. app_dirs,
  42. target=target,
  43. build_dir='build_@t_@w',
  44. config_rules_str=config_rules_str,
  45. build_log_path='build_log.txt',
  46. size_json_path='size.json',
  47. check_warnings=True,
  48. manifest_files=[
  49. str(p) for p in Path(IDF_PATH).glob('**/.build-test-rules.yml')
  50. ],
  51. )
  52. for app in apps:
  53. is_test_related = app.config_name in test_related_app_configs[app.app_dir]
  54. if not preserve_all and not is_test_related:
  55. app.preserve = False
  56. return apps # type: ignore
  57. def get_cmake_apps(
  58. paths: List[str],
  59. target: str,
  60. config_rules_str: List[str],
  61. preserve_all: bool = False,
  62. ) -> List[App]:
  63. ttfw_app_dirs = get_ttfw_app_paths(paths, target)
  64. apps = find_apps(
  65. paths,
  66. recursive=True,
  67. target=target,
  68. build_dir='build_@t_@w',
  69. config_rules_str=config_rules_str,
  70. build_log_path='build_log.txt',
  71. size_json_path='size.json',
  72. check_warnings=True,
  73. preserve=False,
  74. manifest_files=[
  75. str(p) for p in Path(IDF_PATH).glob('**/.build-test-rules.yml')
  76. ],
  77. )
  78. apps_for_build = []
  79. pytest_app_dirs = get_pytest_app_paths(paths, target)
  80. for app in apps:
  81. if preserve_all or app.app_dir in ttfw_app_dirs: # relpath
  82. app.preserve = True
  83. if os.path.realpath(app.app_dir) in pytest_app_dirs:
  84. LOGGER.debug('Skipping build app with pytest scripts: %s', app)
  85. continue
  86. apps_for_build.append(app)
  87. return apps_for_build
  88. APPS_BUILD_PER_JOB = 30
  89. def main(args: argparse.Namespace) -> None:
  90. if args.pytest_apps:
  91. LOGGER.info('Only build apps with pytest scripts')
  92. apps = get_pytest_apps(
  93. args.paths, args.target, args.config, args.marker_expr, args.preserve_all
  94. )
  95. else:
  96. LOGGER.info('build apps. will skip pytest apps with pytest scripts')
  97. apps = get_cmake_apps(args.paths, args.target, args.config, args.preserve_all)
  98. LOGGER.info('Found %d apps after filtering', len(apps))
  99. LOGGER.info(
  100. 'Suggest setting the parallel count to %d for this build job',
  101. len(apps) // APPS_BUILD_PER_JOB + 1,
  102. )
  103. if args.extra_preserve_dirs:
  104. for app in apps:
  105. if app.preserve:
  106. continue
  107. for extra_preserve_dir in args.extra_preserve_dirs:
  108. abs_extra_preserve_dir = Path(extra_preserve_dir).resolve()
  109. abs_app_dir = Path(app.app_dir).resolve()
  110. if (
  111. abs_extra_preserve_dir == abs_app_dir
  112. or abs_extra_preserve_dir in abs_app_dir.parents
  113. ):
  114. app.preserve = True
  115. ret_code = build_apps(
  116. apps,
  117. parallel_count=args.parallel_count,
  118. parallel_index=args.parallel_index,
  119. dry_run=False,
  120. build_verbose=args.build_verbose,
  121. keep_going=True,
  122. collect_size_info=args.collect_size_info,
  123. collect_app_info=args.collect_app_info,
  124. ignore_warning_strs=args.ignore_warning_str,
  125. ignore_warning_file=args.ignore_warning_file,
  126. copy_sdkconfig=args.copy_sdkconfig,
  127. )
  128. sys.exit(ret_code)
  129. if __name__ == '__main__':
  130. parser = argparse.ArgumentParser(
  131. description='Build all the apps for different test types. Will auto remove those non-test apps binaries',
  132. formatter_class=argparse.ArgumentDefaultsHelpFormatter,
  133. )
  134. parser.add_argument('paths', nargs='+', help='Paths to the apps to build.')
  135. parser.add_argument(
  136. '-t',
  137. '--target',
  138. required=True,
  139. help='Build apps for given target. could pass "all" to get apps for all targets',
  140. )
  141. parser.add_argument(
  142. '--config',
  143. default=['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'],
  144. action='append',
  145. help='Adds configurations (sdkconfig file names) to build. This can either be '
  146. 'FILENAME[=NAME] or FILEPATTERN. FILENAME is the name of the sdkconfig file, '
  147. 'relative to the project directory, to be used. Optional NAME can be specified, '
  148. 'which can be used as a name of this configuration. FILEPATTERN is the name of '
  149. 'the sdkconfig file, relative to the project directory, with at most one wildcard. '
  150. 'The part captured by the wildcard is used as the name of the configuration.',
  151. )
  152. parser.add_argument(
  153. '-v',
  154. '--verbose',
  155. action='count',
  156. help='Increase the LOGGER level of the script. Can be specified multiple times.',
  157. )
  158. parser.add_argument(
  159. '--build-verbose',
  160. action='store_true',
  161. help='Enable verbose output from build system.',
  162. )
  163. parser.add_argument(
  164. '--preserve-all',
  165. action='store_true',
  166. help='Preserve the binaries for all apps when specified.',
  167. )
  168. parser.add_argument(
  169. '--parallel-count', default=1, type=int, help='Number of parallel build jobs.'
  170. )
  171. parser.add_argument(
  172. '--parallel-index',
  173. default=1,
  174. type=int,
  175. help='Index (1-based) of the job, out of the number specified by --parallel-count.',
  176. )
  177. parser.add_argument(
  178. '--collect-size-info',
  179. type=argparse.FileType('w'),
  180. help='If specified, the test case name and size info json will be written to this file',
  181. )
  182. parser.add_argument(
  183. '--collect-app-info',
  184. type=argparse.FileType('w'),
  185. help='If specified, the test case name and app info json will be written to this file',
  186. )
  187. parser.add_argument(
  188. '--ignore-warning-str',
  189. action='append',
  190. help='Ignore the warning string that match the specified regex in the build output. '
  191. 'Can be specified multiple times.',
  192. )
  193. parser.add_argument(
  194. '--ignore-warning-file',
  195. default=os.path.join(IDF_PATH, 'tools', 'ci', 'ignore_build_warnings.txt'),
  196. type=argparse.FileType('r'),
  197. help='Ignore the warning strings in the specified file. Each line should be a regex string.',
  198. )
  199. parser.add_argument(
  200. '--copy-sdkconfig',
  201. action='store_true',
  202. help='Copy the sdkconfig file to the build directory.',
  203. )
  204. parser.add_argument(
  205. '--extra-preserve-dirs',
  206. nargs='+',
  207. help='also preserve binaries of the apps under the specified dirs',
  208. )
  209. parser.add_argument(
  210. '--pytest-apps',
  211. action='store_true',
  212. help='Only build apps with pytest scripts. Will build apps without pytest scripts if this flag is unspecified.',
  213. )
  214. parser.add_argument(
  215. '-m',
  216. '--marker-expr',
  217. default='not host_test', # host_test apps would be built and tested under the same job
  218. help='only build tests matching given mark expression. For example: -m "host_test and generic". Works only'
  219. 'for pytest',
  220. )
  221. arguments = parser.parse_args()
  222. setup_logging(arguments.verbose)
  223. main(arguments)