| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- import os
- import sys
- import subprocess
- import logging
- import shutil
- import re
- from .common import BuildSystem, BuildItem, BuildError
- BUILD_SYSTEM_CMAKE = "cmake"
- IDF_PY = "idf.py"
- # While ESP-IDF component CMakeLists files can be identified by the presence of 'idf_component_register' string,
- # there is no equivalent for the project CMakeLists files. This seems to be the best option...
- CMAKE_PROJECT_LINE = r"include($ENV{IDF_PATH}/tools/cmake/project.cmake)"
- SUPPORTED_TARGETS_REGEX = re.compile(r"set\(\s*SUPPORTED_TARGETS\s+([a-z_0-9\- ]+)\s*\)")
- class CMakeBuildSystem(BuildSystem):
- NAME = BUILD_SYSTEM_CMAKE
- @staticmethod
- def build(build_item): # type: (BuildItem) -> None
- app_path = build_item.app_dir
- work_path = build_item.work_dir or app_path
- if not build_item.build_dir:
- build_path = os.path.join(work_path, "build")
- elif os.path.isabs(build_item.build_dir):
- build_path = build_item.build_dir
- else:
- build_path = os.path.join(work_path, build_item.build_dir)
- if work_path != app_path:
- if os.path.exists(work_path):
- logging.debug("Work directory {} exists, removing".format(work_path))
- if not build_item.dry_run:
- shutil.rmtree(work_path)
- logging.debug("Copying app from {} to {}".format(app_path, work_path))
- if not build_item.dry_run:
- shutil.copytree(app_path, work_path)
- if os.path.exists(build_path):
- logging.debug("Build directory {} exists, removing".format(build_path))
- if not build_item.dry_run:
- shutil.rmtree(build_path)
- if not build_item.dry_run:
- os.makedirs(build_path)
- # Prepare the sdkconfig file, from the contents of sdkconfig.defaults (if exists) and the contents of
- # build_info.sdkconfig_path, i.e. the config-specific sdkconfig file.
- #
- # Note: the build system supports taking multiple sdkconfig.defaults files via SDKCONFIG_DEFAULTS
- # CMake variable. However here we do this manually to perform environment variable expansion in the
- # sdkconfig files.
- sdkconfig_defaults_list = ["sdkconfig.defaults"]
- if build_item.sdkconfig_path:
- sdkconfig_defaults_list.append(build_item.sdkconfig_path)
- sdkconfig_file = os.path.join(work_path, "sdkconfig")
- if os.path.exists(sdkconfig_file):
- logging.debug("Removing sdkconfig file: {}".format(sdkconfig_file))
- if not build_item.dry_run:
- os.unlink(sdkconfig_file)
- logging.debug("Creating sdkconfig file: {}".format(sdkconfig_file))
- if not build_item.dry_run:
- with open(sdkconfig_file, "w") as f_out:
- for sdkconfig_name in sdkconfig_defaults_list:
- sdkconfig_path = os.path.join(work_path, sdkconfig_name)
- if not sdkconfig_path or not os.path.exists(sdkconfig_path):
- continue
- logging.debug("Appending {} to sdkconfig".format(sdkconfig_name))
- with open(sdkconfig_path, "r") as f_in:
- for line in f_in:
- f_out.write(os.path.expandvars(line))
- # Also save the sdkconfig file in the build directory
- shutil.copyfile(
- os.path.join(work_path, "sdkconfig"),
- os.path.join(build_path, "sdkconfig"),
- )
- else:
- for sdkconfig_name in sdkconfig_defaults_list:
- sdkconfig_path = os.path.join(app_path, sdkconfig_name)
- if not sdkconfig_path:
- continue
- logging.debug("Considering sdkconfig {}".format(sdkconfig_path))
- if not os.path.exists(sdkconfig_path):
- continue
- logging.debug("Appending {} to sdkconfig".format(sdkconfig_name))
- # Prepare the build arguments
- args = [
- # Assume it is the responsibility of the caller to
- # set up the environment (run . ./export.sh)
- IDF_PY,
- "-B",
- build_path,
- "-C",
- work_path,
- "-DIDF_TARGET=" + build_item.target,
- ]
- if build_item.verbose:
- args.append("-v")
- args.append("build")
- cmdline = format(" ".join(args))
- logging.info("Running {}".format(cmdline))
- if build_item.dry_run:
- return
- log_file = None
- build_stdout = sys.stdout
- build_stderr = sys.stderr
- if build_item.build_log_path:
- logging.info("Writing build log to {}".format(build_item.build_log_path))
- log_file = open(build_item.build_log_path, "w")
- build_stdout = log_file
- build_stderr = log_file
- try:
- subprocess.check_call(args, stdout=build_stdout, stderr=build_stderr)
- except subprocess.CalledProcessError as e:
- raise BuildError("Build failed with exit code {}".format(e.returncode))
- finally:
- if log_file:
- log_file.close()
- @staticmethod
- def _read_cmakelists(app_path):
- cmakelists_path = os.path.join(app_path, "CMakeLists.txt")
- if not os.path.exists(cmakelists_path):
- return None
- with open(cmakelists_path, "r") as cmakelists_file:
- return cmakelists_file.read()
- @staticmethod
- def is_app(path):
- cmakelists_file_content = CMakeBuildSystem._read_cmakelists(path)
- if not cmakelists_file_content:
- return False
- if CMAKE_PROJECT_LINE not in cmakelists_file_content:
- return False
- return True
- @staticmethod
- def supported_targets(app_path):
- cmakelists_file_content = CMakeBuildSystem._read_cmakelists(app_path)
- if not cmakelists_file_content:
- return None
- match = re.findall(SUPPORTED_TARGETS_REGEX, cmakelists_file_content)
- if not match:
- return None
- if len(match) > 1:
- raise NotImplementedError("Can't determine the value of SUPPORTED_TARGETS in {}".format(app_path))
- targets = match[0].split(" ")
- return targets
|