ff427c9650
If trailing newline is missing, the last line of that item gets merged with the first line of the next item.
160 lines
6.3 KiB
Python
160 lines
6.3 KiB
Python
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", "sdkconfig.defaults." + build_item.target]
|
|
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:
|
|
if not line.endswith("\n"):
|
|
line += "\n"
|
|
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
|