idf_utils.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import logging
  4. import os
  5. import re
  6. import shutil
  7. import subprocess
  8. import sys
  9. import typing
  10. from pathlib import Path
  11. from typing import Pattern, Union
  12. try:
  13. EXT_IDF_PATH = os.environ['IDF_PATH'] # type: str
  14. except KeyError:
  15. print('IDF_PATH must be set before running this test', file=sys.stderr)
  16. exit(1)
  17. EnvDict = typing.Dict[str, str]
  18. IdfPyFunc = typing.Callable[..., subprocess.CompletedProcess]
  19. def find_python(path_var: str) -> str:
  20. """
  21. Find python interpreter in the paths specified in the given PATH variable.
  22. Returns the full path to the interpreter.
  23. """
  24. res = shutil.which('python', path=path_var)
  25. if res is None:
  26. raise ValueError('python not found')
  27. return res
  28. def get_idf_build_env(idf_path: str) -> EnvDict:
  29. """
  30. Get environment variables (as set by export.sh) for the specific IDF copy
  31. :param idf_path: path of the IDF copy to use
  32. :return: dictionary of environment variables and their values
  33. """
  34. cmd = [
  35. sys.executable,
  36. os.path.join(idf_path, 'tools', 'idf_tools.py'),
  37. 'export',
  38. '--format=key-value'
  39. ]
  40. keys_values = subprocess.check_output(cmd, stderr=subprocess.PIPE).decode()
  41. idf_tool_py_env = {key: os.path.expandvars(value) for key, value in
  42. [line.split('=') for line in keys_values.splitlines()]}
  43. env_vars = {} # type: EnvDict
  44. env_vars.update(os.environ)
  45. env_vars.update(idf_tool_py_env)
  46. # not set by idf_tools.py, normally set by export.sh
  47. env_vars['IDF_PATH'] = idf_path
  48. return env_vars
  49. def run_idf_py(*args: str,
  50. env: typing.Optional[EnvDict] = None,
  51. idf_path: typing.Optional[typing.Union[str,Path]] = None,
  52. workdir: typing.Optional[str] = None,
  53. check: bool = True,
  54. python: typing.Optional[str] = None,
  55. input_str: typing.Optional[str] = None) -> subprocess.CompletedProcess:
  56. """
  57. Run idf.py command with given arguments, raise an exception on failure
  58. :param args: arguments to pass to idf.py
  59. :param env: environment variables to run the build with; if not set, the default environment is used
  60. :param idf_path: path to the IDF copy to use; if not set, IDF_PATH from the 'env' argument is used
  61. :param workdir: directory where to run the build; if not set, the current directory is used
  62. :param check: check process exits with a zero exit code, if false all retvals are accepted without failing the test
  63. :param python: absolute path to python interpreter
  64. :param input_str: input to idf.py
  65. """
  66. if not env:
  67. env = dict(**os.environ)
  68. if not workdir:
  69. workdir = os.getcwd()
  70. # order: function argument -> value in env dictionary -> system environment
  71. if idf_path is None:
  72. idf_path = env.get('IDF_PATH')
  73. if not idf_path:
  74. raise ValueError('IDF_PATH must be set in the env array if idf_path argument is not set')
  75. if python is None:
  76. python = find_python(env['PATH'])
  77. cmd = [
  78. python,
  79. os.path.join(idf_path, 'tools', 'idf.py')
  80. ]
  81. cmd += args # type: ignore
  82. logging.debug('running {} in {}'.format(' '.join(cmd), workdir))
  83. return subprocess.run(
  84. cmd, env=env, cwd=workdir,
  85. check=check, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  86. text=True, encoding='utf-8', errors='backslashreplace', input=input_str)
  87. def run_cmake(*cmake_args: str,
  88. env: typing.Optional[EnvDict] = None,
  89. check: bool = True,
  90. workdir: typing.Optional[Union[Path,str]] = None) -> subprocess.CompletedProcess:
  91. """
  92. Run cmake command with given arguments, raise an exception on failure
  93. :param cmake_args: arguments to pass cmake
  94. :param env: environment variables to run the cmake with; if not set, the default environment is used
  95. :param check: check process exits with a zero exit code, if false all retvals are accepted without failing the test
  96. :param workdir: directory where to run cmake; if not set, the current directory is used
  97. """
  98. if not env:
  99. env = dict(**os.environ)
  100. if workdir:
  101. build_dir = Path(workdir, 'build')
  102. else:
  103. build_dir = (Path(os.getcwd()) / 'build')
  104. build_dir.mkdir(parents=True, exist_ok=True)
  105. cmd = ['cmake'] + list(cmake_args)
  106. logging.debug('running {} in {}'.format(' '.join(cmd), build_dir))
  107. return subprocess.run(
  108. cmd, env=env, cwd=build_dir,
  109. check=check, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  110. text=True, encoding='utf-8', errors='backslashreplace')
  111. def run_cmake_and_build(*cmake_args: str, env: typing.Optional[EnvDict] = None) -> None:
  112. """
  113. Run cmake command with given arguments and build afterwards, raise an exception on failure
  114. :param cmake_args: arguments to pass cmake
  115. :param env: environment variables to run the cmake with; if not set, the default environment is used
  116. """
  117. run_cmake(*cmake_args, env=env)
  118. run_cmake('--build', '.')
  119. def file_contains(filename: Union[str, Path], what: Union[str, Pattern]) -> bool:
  120. """
  121. Returns true if file contains required object
  122. :param filename: path to file where lookup is executed
  123. :param what: searched substring or regex object
  124. """
  125. with open(filename, 'r', encoding='utf-8') as f:
  126. data = f.read()
  127. if isinstance(what, str):
  128. return what in data
  129. else:
  130. return re.search(what, data) is not None
  131. def bin_file_contains(filename: Union[str, Path], what: bytearray) -> bool:
  132. """
  133. Returns true if the binary file contains the given string
  134. :param filename: path to file where lookup is executed
  135. :param what: searched bytes
  136. """
  137. with open(filename, 'rb') as f:
  138. data = f.read()
  139. return data.find(what) != -1