cmake.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import os
  2. import sys
  3. import subprocess
  4. import logging
  5. import shutil
  6. import re
  7. from .common import BuildSystem, BuildItem, BuildError
  8. BUILD_SYSTEM_CMAKE = "cmake"
  9. IDF_PY = "idf.py"
  10. # While ESP-IDF component CMakeLists files can be identified by the presence of 'idf_component_register' string,
  11. # there is no equivalent for the project CMakeLists files. This seems to be the best option...
  12. CMAKE_PROJECT_LINE = r"include($ENV{IDF_PATH}/tools/cmake/project.cmake)"
  13. SUPPORTED_TARGETS_REGEX = re.compile(r"set\(\s*SUPPORTED_TARGETS\s+([a-z_0-9\- ]+)\s*\)")
  14. class CMakeBuildSystem(BuildSystem):
  15. NAME = BUILD_SYSTEM_CMAKE
  16. @staticmethod
  17. def build(build_item): # type: (BuildItem) -> None
  18. app_path = build_item.app_dir
  19. work_path = build_item.work_dir or app_path
  20. if not build_item.build_dir:
  21. build_path = os.path.join(work_path, "build")
  22. elif os.path.isabs(build_item.build_dir):
  23. build_path = build_item.build_dir
  24. else:
  25. build_path = os.path.join(work_path, build_item.build_dir)
  26. if work_path != app_path:
  27. if os.path.exists(work_path):
  28. logging.debug("Work directory {} exists, removing".format(work_path))
  29. if not build_item.dry_run:
  30. shutil.rmtree(work_path)
  31. logging.debug("Copying app from {} to {}".format(app_path, work_path))
  32. if not build_item.dry_run:
  33. shutil.copytree(app_path, work_path)
  34. if os.path.exists(build_path):
  35. logging.debug("Build directory {} exists, removing".format(build_path))
  36. if not build_item.dry_run:
  37. shutil.rmtree(build_path)
  38. if not build_item.dry_run:
  39. os.makedirs(build_path)
  40. # Prepare the sdkconfig file, from the contents of sdkconfig.defaults (if exists) and the contents of
  41. # build_info.sdkconfig_path, i.e. the config-specific sdkconfig file.
  42. #
  43. # Note: the build system supports taking multiple sdkconfig.defaults files via SDKCONFIG_DEFAULTS
  44. # CMake variable. However here we do this manually to perform environment variable expansion in the
  45. # sdkconfig files.
  46. sdkconfig_defaults_list = ["sdkconfig.defaults", "sdkconfig.defaults." + build_item.target]
  47. if build_item.sdkconfig_path:
  48. sdkconfig_defaults_list.append(build_item.sdkconfig_path)
  49. sdkconfig_file = os.path.join(work_path, "sdkconfig")
  50. if os.path.exists(sdkconfig_file):
  51. logging.debug("Removing sdkconfig file: {}".format(sdkconfig_file))
  52. if not build_item.dry_run:
  53. os.unlink(sdkconfig_file)
  54. logging.debug("Creating sdkconfig file: {}".format(sdkconfig_file))
  55. if not build_item.dry_run:
  56. with open(sdkconfig_file, "w") as f_out:
  57. for sdkconfig_name in sdkconfig_defaults_list:
  58. sdkconfig_path = os.path.join(work_path, sdkconfig_name)
  59. if not sdkconfig_path or not os.path.exists(sdkconfig_path):
  60. continue
  61. logging.debug("Appending {} to sdkconfig".format(sdkconfig_name))
  62. with open(sdkconfig_path, "r") as f_in:
  63. for line in f_in:
  64. if not line.endswith("\n"):
  65. line += "\n"
  66. f_out.write(os.path.expandvars(line))
  67. # Also save the sdkconfig file in the build directory
  68. shutil.copyfile(
  69. os.path.join(work_path, "sdkconfig"),
  70. os.path.join(build_path, "sdkconfig"),
  71. )
  72. else:
  73. for sdkconfig_name in sdkconfig_defaults_list:
  74. sdkconfig_path = os.path.join(app_path, sdkconfig_name)
  75. if not sdkconfig_path:
  76. continue
  77. logging.debug("Considering sdkconfig {}".format(sdkconfig_path))
  78. if not os.path.exists(sdkconfig_path):
  79. continue
  80. logging.debug("Appending {} to sdkconfig".format(sdkconfig_name))
  81. # Prepare the build arguments
  82. args = [
  83. # Assume it is the responsibility of the caller to
  84. # set up the environment (run . ./export.sh)
  85. IDF_PY,
  86. "-B",
  87. build_path,
  88. "-C",
  89. work_path,
  90. "-DIDF_TARGET=" + build_item.target,
  91. ]
  92. if build_item.verbose:
  93. args.append("-v")
  94. args.append("build")
  95. cmdline = format(" ".join(args))
  96. logging.info("Running {}".format(cmdline))
  97. if build_item.dry_run:
  98. return
  99. log_file = None
  100. build_stdout = sys.stdout
  101. build_stderr = sys.stderr
  102. if build_item.build_log_path:
  103. logging.info("Writing build log to {}".format(build_item.build_log_path))
  104. log_file = open(build_item.build_log_path, "w")
  105. build_stdout = log_file
  106. build_stderr = log_file
  107. try:
  108. subprocess.check_call(args, stdout=build_stdout, stderr=build_stderr)
  109. except subprocess.CalledProcessError as e:
  110. raise BuildError("Build failed with exit code {}".format(e.returncode))
  111. finally:
  112. if log_file:
  113. log_file.close()
  114. @staticmethod
  115. def _read_cmakelists(app_path):
  116. cmakelists_path = os.path.join(app_path, "CMakeLists.txt")
  117. if not os.path.exists(cmakelists_path):
  118. return None
  119. with open(cmakelists_path, "r") as cmakelists_file:
  120. return cmakelists_file.read()
  121. @staticmethod
  122. def is_app(path):
  123. cmakelists_file_content = CMakeBuildSystem._read_cmakelists(path)
  124. if not cmakelists_file_content:
  125. return False
  126. if CMAKE_PROJECT_LINE not in cmakelists_file_content:
  127. return False
  128. return True
  129. @staticmethod
  130. def supported_targets(app_path):
  131. cmakelists_file_content = CMakeBuildSystem._read_cmakelists(app_path)
  132. if not cmakelists_file_content:
  133. return None
  134. match = re.findall(SUPPORTED_TARGETS_REGEX, cmakelists_file_content)
  135. if not match:
  136. return None
  137. if len(match) > 1:
  138. raise NotImplementedError("Can't determine the value of SUPPORTED_TARGETS in {}".format(app_path))
  139. targets = match[0].split(" ")
  140. return targets