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