build_pytest_apps.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. """
  4. This file is used to generate binary files for the given path.
  5. """
  6. import argparse
  7. import copy
  8. import logging
  9. import os
  10. import sys
  11. from collections import defaultdict
  12. from typing import List
  13. from idf_ci_utils import IDF_PATH, PytestCase, get_pytest_cases
  14. try:
  15. from build_apps import build_apps
  16. from find_apps import find_builds_for_app
  17. from find_build_apps import BuildItem, CMakeBuildSystem, config_rules_from_str, setup_logging
  18. except ImportError:
  19. sys.path.append(os.path.join(IDF_PATH, 'tools'))
  20. from build_apps import build_apps
  21. from find_apps import find_builds_for_app
  22. from find_build_apps import BuildItem, CMakeBuildSystem, config_rules_from_str, setup_logging
  23. def main(args: argparse.Namespace) -> None:
  24. pytest_cases: List[PytestCase] = []
  25. for path in args.paths:
  26. pytest_cases += get_pytest_cases(path, args.target, args.marker_expr)
  27. paths = set()
  28. app_configs = defaultdict(set)
  29. for case in pytest_cases:
  30. for app in case.apps:
  31. paths.add(app.path)
  32. app_configs[app.path].add(app.config)
  33. app_dirs = list(paths)
  34. if not app_dirs:
  35. raise RuntimeError('No apps found')
  36. logging.info(f'Found {len(app_dirs)} apps')
  37. app_dirs.sort()
  38. # Find compatible configurations of each app, collect them as BuildItems
  39. build_items: List[BuildItem] = []
  40. config_rules = config_rules_from_str(args.config or [])
  41. for app_dir in app_dirs:
  42. app_dir = os.path.realpath(app_dir)
  43. if args.target in CMakeBuildSystem.supported_targets(app_dir):
  44. build_items += find_builds_for_app(
  45. app_path=app_dir,
  46. work_dir=app_dir,
  47. build_dir='build_@t_@w',
  48. build_log=f'{app_dir}/build_@t_@w/build.log',
  49. target_arg=args.target,
  50. build_system='cmake',
  51. config_rules=config_rules,
  52. )
  53. modified_build_items = []
  54. # auto clean up the binaries if no flag --preserve-all
  55. for item in build_items:
  56. is_test_related = item.config_name in app_configs[item.app_dir]
  57. if args.test_only and not is_test_related:
  58. logging.info(f'Skipping non-test app: {item}')
  59. continue
  60. copied_item = copy.deepcopy(item)
  61. if not args.preserve_all and not is_test_related:
  62. copied_item.preserve = False
  63. modified_build_items.append(copied_item)
  64. logging.info(f'Found {len(modified_build_items)} builds')
  65. modified_build_items.sort(key=lambda x: x.build_path) # type: ignore
  66. build_apps(
  67. build_items=modified_build_items,
  68. parallel_count=args.parallel_count,
  69. parallel_index=args.parallel_index,
  70. dry_run=False,
  71. build_verbose=args.build_verbose,
  72. keep_going=True,
  73. output_build_list=None,
  74. size_info=args.size_info,
  75. )
  76. if __name__ == '__main__':
  77. parser = argparse.ArgumentParser(
  78. description='Build all the pytest apps under specified paths. Will auto remove those non-test apps binaries'
  79. )
  80. parser.add_argument(
  81. '-t', '--target', required=True, help='Build apps for given target.'
  82. )
  83. parser.add_argument(
  84. '-m',
  85. '--marker-expr',
  86. default='not host_test', # host_test apps would be built and tested under the same job
  87. help='only build tests matching given mark expression. For example: -m "host_test and generic".',
  88. )
  89. parser.add_argument(
  90. '--config',
  91. default=['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'],
  92. action='append',
  93. help='Adds configurations (sdkconfig file names) to build. This can either be '
  94. + 'FILENAME[=NAME] or FILEPATTERN. FILENAME is the name of the sdkconfig file, '
  95. + 'relative to the project directory, to be used. Optional NAME can be specified, '
  96. + 'which can be used as a name of this configuration. FILEPATTERN is the name of '
  97. + 'the sdkconfig file, relative to the project directory, with at most one wildcard. '
  98. + 'The part captured by the wildcard is used as the name of the configuration.',
  99. )
  100. parser.add_argument(
  101. 'paths',
  102. nargs='+',
  103. help='One or more app paths. Will use the current path if not specified.',
  104. )
  105. parser.add_argument(
  106. '--parallel-count', default=1, type=int, help='Number of parallel build jobs.'
  107. )
  108. parser.add_argument(
  109. '--parallel-index',
  110. default=1,
  111. type=int,
  112. help='Index (1-based) of the job, out of the number specified by --parallel-count.',
  113. )
  114. parser.add_argument(
  115. '--size-info',
  116. type=argparse.FileType('a'),
  117. help='If specified, the test case name and size info json will be written to this file',
  118. )
  119. parser.add_argument(
  120. '-v',
  121. '--verbose',
  122. action='count',
  123. help='Increase the logging level of the script. Can be specified multiple times.',
  124. )
  125. parser.add_argument(
  126. '--build-verbose',
  127. action='store_true',
  128. help='Enable verbose output from build system.',
  129. )
  130. parser.add_argument(
  131. '--preserve-all',
  132. action='store_true',
  133. help='Preserve the binaries for all apps when specified.',
  134. )
  135. parser.add_argument(
  136. '--test-only',
  137. action='store_true',
  138. help='Build only test related app when specified.',
  139. )
  140. arguments = parser.parse_args()
  141. setup_logging(arguments)
  142. main(arguments)