mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-11-04 00:51:42 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			7.9 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'Supported [Tt]argets((?:[\s|]+(?:ESP[0-9A-Z\-]+))+)')
 | 
						|
 | 
						|
FORMAL_TO_USUAL = {
 | 
						|
    'ESP32': 'esp32',
 | 
						|
    'ESP32-S2': 'esp32s2',
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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 _read_readme(app_path):
 | 
						|
        # Markdown supported targets should be:
 | 
						|
        # e.g. | Supported Targets | ESP32 |
 | 
						|
        #      | ----------------- | ----- |
 | 
						|
        # reStructuredText supported targets should be:
 | 
						|
        # e.g. ================= =====
 | 
						|
        #      Supported Targets ESP32
 | 
						|
        #      ================= =====
 | 
						|
        def get_md_or_rst(app_path):
 | 
						|
            readme_path = os.path.join(app_path, 'README.md')
 | 
						|
            if not os.path.exists(readme_path):
 | 
						|
                readme_path = os.path.join(app_path, 'README.rst')
 | 
						|
                if not os.path.exists(readme_path):
 | 
						|
                    return None
 | 
						|
            return readme_path
 | 
						|
 | 
						|
        readme_path = get_md_or_rst(app_path)
 | 
						|
        # Handle sub apps situation, e.g. master-slave
 | 
						|
        if not readme_path:
 | 
						|
            readme_path = get_md_or_rst(os.path.dirname(app_path))
 | 
						|
        if not readme_path:
 | 
						|
            return None
 | 
						|
        with open(readme_path, "r") as readme_file:
 | 
						|
            return readme_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):
 | 
						|
        readme_file_content = CMakeBuildSystem._read_readme(app_path)
 | 
						|
        if not readme_file_content:
 | 
						|
            return None
 | 
						|
        match = re.findall(SUPPORTED_TARGETS_REGEX, readme_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))
 | 
						|
        support_str = match[0].strip()
 | 
						|
 | 
						|
        targets = []
 | 
						|
        for part in support_str.split('|'):
 | 
						|
            for inner in part.split(' '):
 | 
						|
                inner = inner.strip()
 | 
						|
                if not inner:
 | 
						|
                    continue
 | 
						|
                elif inner in FORMAL_TO_USUAL:
 | 
						|
                    targets.append(FORMAL_TO_USUAL[inner])
 | 
						|
                else:
 | 
						|
                    raise NotImplementedError("Can't recognize value of target {} in {}, now we only support '{}'"
 | 
						|
                                              .format(inner, app_path, ', '.join(FORMAL_TO_USUAL.keys())))
 | 
						|
        return targets
 |