build_examples.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #!/usr/bin/env -S python3 -B
  2. # Copyright (c) 2021 Project CHIP Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import json
  16. import logging
  17. import os
  18. import sys
  19. import click
  20. import coloredlogs
  21. from builders.builder import BuilderOptions
  22. from runner import PrintOnlyRunner, ShellRunner
  23. import build
  24. sys.path.append(os.path.abspath(os.path.dirname(__file__)))
  25. # Supported log levels, mapping string values required for argument
  26. # parsing into logging constants
  27. __LOG_LEVELS__ = {
  28. 'debug': logging.DEBUG,
  29. 'info': logging.INFO,
  30. 'warn': logging.WARN,
  31. 'fatal': logging.FATAL,
  32. }
  33. def CommaSeparate(items) -> str:
  34. return ', '.join([x for x in items])
  35. def ValidateRepoPath(context, parameter, value):
  36. """
  37. Validates that the given path looks like a valid chip repository checkout.
  38. """
  39. if value.startswith('/TEST/'):
  40. # Hackish command to allow for unit testing
  41. return value
  42. for name in ['BUILD.gn', '.gn', os.path.join('scripts', 'bootstrap.sh')]:
  43. expected_file = os.path.join(value, name)
  44. if not os.path.exists(expected_file):
  45. raise click.BadParameter(
  46. ("'%s' does not look like a valid repository path: "
  47. "%s not found.") % (value, expected_file))
  48. return value
  49. def ValidateTargetNames(context, parameter, values):
  50. """
  51. Validates that the given target name is valid.
  52. """
  53. for value in values:
  54. if not any(target.StringIntoTargetParts(value.lower())
  55. for target in build.targets.BUILD_TARGETS):
  56. raise click.BadParameter(
  57. "'%s' is not a valid target name." % value)
  58. return values
  59. @click.group(chain=True)
  60. @click.option(
  61. '--log-level',
  62. default='INFO',
  63. type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False),
  64. help='Determines the verbosity of script output.')
  65. @click.option(
  66. '--target',
  67. default=[],
  68. multiple=True,
  69. callback=ValidateTargetNames,
  70. help='Build target(s)'
  71. )
  72. @click.option(
  73. '--enable-flashbundle',
  74. default=False,
  75. is_flag=True,
  76. help='Also generate the flashbundles for the app.'
  77. )
  78. @click.option(
  79. '--repo',
  80. default='.',
  81. callback=ValidateRepoPath,
  82. help='Path to the root of the CHIP SDK repository checkout.')
  83. @click.option(
  84. '--out-prefix',
  85. default='./out',
  86. type=click.Path(file_okay=False, resolve_path=True),
  87. help='Prefix for the generated file output.')
  88. @click.option(
  89. '--pregen-dir',
  90. default=None,
  91. type=click.Path(file_okay=False, resolve_path=True),
  92. help='Directory where generated files have been pre-generated.')
  93. @click.option(
  94. '--clean',
  95. default=False,
  96. is_flag=True,
  97. help='Clean output directory before running the command')
  98. @click.option(
  99. '--dry-run',
  100. default=False,
  101. is_flag=True,
  102. help='Only print out shell commands that would be executed')
  103. @click.option(
  104. '--dry-run-output',
  105. default="-",
  106. type=click.File("wt"),
  107. help='Where to write the dry run output')
  108. @click.option(
  109. '--no-log-timestamps',
  110. default=False,
  111. is_flag=True,
  112. help='Skip timestaps in log output')
  113. @click.option(
  114. '--pw-command-launcher',
  115. help=(
  116. 'Set pigweed command launcher. E.g.: "--pw-command-launcher=ccache" '
  117. 'for using ccache when building examples.'))
  118. @click.pass_context
  119. def main(context, log_level, target, repo,
  120. out_prefix, pregen_dir, clean, dry_run, dry_run_output, enable_flashbundle,
  121. no_log_timestamps, pw_command_launcher):
  122. # Ensures somewhat pretty logging of what is going on
  123. log_fmt = '%(asctime)s %(levelname)-7s %(message)s'
  124. if no_log_timestamps:
  125. log_fmt = '%(levelname)-7s %(message)s'
  126. coloredlogs.install(level=__LOG_LEVELS__[log_level], fmt=log_fmt)
  127. if 'PW_PROJECT_ROOT' not in os.environ:
  128. raise click.UsageError("""
  129. PW_PROJECT_ROOT not set in the current environment.
  130. Please make sure you `source scripts/bootstrap.sh` or `source scripts/activate.sh`
  131. before running this script.
  132. """.strip())
  133. if dry_run:
  134. runner = PrintOnlyRunner(dry_run_output, root=repo)
  135. else:
  136. runner = ShellRunner(root=repo)
  137. requested_targets = set([t.lower() for t in target])
  138. logging.info('Building targets: %s', CommaSeparate(requested_targets))
  139. context.obj = build.Context(
  140. repository_path=repo, output_prefix=out_prefix, runner=runner)
  141. context.obj.SetupBuilders(targets=requested_targets, options=BuilderOptions(
  142. enable_flashbundle=enable_flashbundle,
  143. pw_command_launcher=pw_command_launcher,
  144. pregen_dir=pregen_dir,
  145. ))
  146. if clean:
  147. context.obj.CleanOutputDirectories()
  148. @main.command(
  149. 'gen', help='Generate ninja/makefiles (but does not run the compilation)')
  150. @click.pass_context
  151. def cmd_generate(context):
  152. context.obj.Generate()
  153. @main.command(
  154. 'targets',
  155. help=('Lists the targets that can be used with the build and gen commands'))
  156. @click.option(
  157. '--format',
  158. default='summary',
  159. type=click.Choice(['summary', 'expanded', 'json'], case_sensitive=False),
  160. help="""
  161. summary - list of shorthand strings summarzing the available targets;
  162. expanded - list all possible targets rather than the shorthand string;
  163. json - a JSON representation of the available targets
  164. """)
  165. @click.pass_context
  166. def cmd_targets(context, format):
  167. if format == 'expanded':
  168. for target in build.targets.BUILD_TARGETS:
  169. build.target.report_rejected_parts = False
  170. for s in target.AllVariants():
  171. print(s)
  172. elif format == 'json':
  173. print(json.dumps([target.ToDict() for target in build.targets.BUILD_TARGETS], indent=4))
  174. else:
  175. for target in build.targets.BUILD_TARGETS:
  176. print(target.HumanString())
  177. @main.command('build', help='generate and run ninja/make as needed to compile')
  178. @click.option(
  179. '--copy-artifacts-to',
  180. default=None,
  181. type=click.Path(file_okay=False, resolve_path=True),
  182. help='Prefix for the generated file output.')
  183. @click.option(
  184. '--create-archives',
  185. default=None,
  186. type=click.Path(file_okay=False, resolve_path=True),
  187. help='Prefix of compressed archives of the generated files.')
  188. @click.pass_context
  189. def cmd_build(context, copy_artifacts_to, create_archives):
  190. context.obj.Build()
  191. if copy_artifacts_to:
  192. context.obj.CopyArtifactsTo(copy_artifacts_to)
  193. if create_archives:
  194. context.obj.CreateArtifactArchives(create_archives)
  195. if __name__ == '__main__':
  196. main(auto_envvar_prefix='CHIP')