1import logging
2import os
3import shutil
4import subprocess
5import sys
6
7from .common import BuildError, BuildItem, BuildSystem
8
9try:
10    from typing import Any, Optional
11except ImportError:
12    pass
13
14BUILD_SYSTEM_CMAKE = 'cmake'
15IDF_PY = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py')
16
17# While ESP-IDF component CMakeLists files can be identified by the presence of 'idf_component_register' string,
18# there is no equivalent for the project CMakeLists files. This seems to be the best option...
19CMAKE_PROJECT_LINE = r'include($ENV{IDF_PATH}/tools/cmake/project.cmake)'
20
21
22class CMakeBuildSystem(BuildSystem):
23    NAME = BUILD_SYSTEM_CMAKE
24
25    @classmethod
26    def build(cls, build_item):  # type: (BuildItem) -> None
27        build_path, work_path, extra_cmakecache_items = cls.build_prepare(build_item)
28        # Prepare the build arguments
29        args = [
30            sys.executable,
31            IDF_PY,
32            '-B',
33            build_path,
34            '-C',
35            work_path,
36            '-DIDF_TARGET=' + build_item.target,
37        ]
38        if extra_cmakecache_items:
39            for key, val in extra_cmakecache_items.items():
40                args.append('-D{}={}'.format(key, val))
41            if 'TEST_EXCLUDE_COMPONENTS' in extra_cmakecache_items \
42                    and 'TEST_COMPONENTS' not in extra_cmakecache_items:
43                args.append('-DTESTS_ALL=1')
44        if build_item.verbose:
45            args.append('-v')
46        if 'CONFIG_APP_BUILD_BOOTLOADER' in extra_cmakecache_items:
47            # In case if secure_boot is enabled then for bootloader build need to add `bootloader` cmd
48            args.append('bootloader')
49        args.append('build')
50        cmdline = format(' '.join(args))
51        logging.info('Running {}'.format(cmdline))
52
53        if build_item.dry_run:
54            return
55
56        log_file = None
57        build_stdout = sys.stdout
58        build_stderr = sys.stderr
59        if build_item.build_log_path:
60            logging.info('Writing build log to {}'.format(build_item.build_log_path))
61            log_file = open(build_item.build_log_path, 'w')
62            build_stdout = log_file
63            build_stderr = log_file
64
65        try:
66            subprocess.check_call(args, stdout=build_stdout, stderr=build_stderr)
67        except subprocess.CalledProcessError as e:
68            raise BuildError('Build failed with exit code {}'.format(e.returncode))
69        else:
70            # Also save the sdkconfig file in the build directory
71            shutil.copyfile(
72                os.path.join(work_path, 'sdkconfig'),
73                os.path.join(build_path, 'sdkconfig'),
74            )
75            build_item.size_json_fp = build_item.get_size_json_fp()
76        finally:
77            if log_file:
78                log_file.close()
79
80    @staticmethod
81    def _read_cmakelists(app_path):  # type: (str) -> Optional[str]
82        cmakelists_path = os.path.join(app_path, 'CMakeLists.txt')
83        if not os.path.exists(cmakelists_path):
84            return None
85        with open(cmakelists_path, 'r') as cmakelists_file:
86            return cmakelists_file.read()
87
88    @staticmethod
89    def is_app(path):  # type: (str) -> bool
90        cmakelists_file_content = CMakeBuildSystem._read_cmakelists(path)
91        if not cmakelists_file_content:
92            return False
93        if CMAKE_PROJECT_LINE not in cmakelists_file_content:
94            return False
95        return True
96
97    @classmethod
98    def supported_targets(cls, app_path):  # type: (str) -> Any
99        return cls._supported_targets(app_path)
100