import os import json import subprocess from build import * from pathlib import Path as SysPath from cache import Cache from env import Environment from platform import Platform from target import Target from path import Path class CMakeBuildSystem(BuildSystem): def __init__(self, **kwargs): name = kwargs.get('name', None) source_dir = kwargs.get('source_dir', None) bin_dir = kwargs.get('bin_dir', None) parent = kwargs.get('parent', None) self.__data = kwargs.get('data', None) super().__init__(**kwargs) def build_for(self): if self.__data is None or 'build_for' not in self.__data: return 'target' return self.__data['build_for'] def __get_watched_files(self): result = [] ns = self.get_path_namespaces() if 'watch_files' in self.__data: watch_list = self.__data['watch_files'] for p in watch_list: path = Path(p) real_paths = path.expand_path(relative_to=ns) for p2 in real_paths: result.append(p2) manifest_path = os.path.join(self.binary_dir(), 'compile_commands.json') manifest_file = None manifest = None try: manifest_file = open(manifest_path, mode='r') except: return result try: manifest = json.load(manifest_file) except: return result for v in manifest: if 'file' not in v: continue path = v['file'] result.append(path) return result def __get_build_mtime(self): files = self.__get_watched_files() result = 0 for path in files: mtime = os.path.getmtime(path) if mtime > result: result = mtime return result def __get_configure_mtime(self): source_dir = os.path.abspath(self.source_dir()) result = 0 for root, dirs, files in os.walk(source_dir): for f in files: if f != 'CMakeLists.txt' and not f.endswith('.cmake'): continue target_path = os.path.join(root, f) mtime = os.path.getmtime(target_path) if mtime > result: result = mtime return result def _is_configuration_out_of_date(self): cache_file = os.path.join(self.binary_dir(), 'CMakeCache.txt') if not os.path.isfile(cache_file): return True current_mtime = self.__get_configure_mtime() target_mtime = Cache.get(f'component.{self.name()}.configure-mtime') if target_mtime is None: target_mtime = 0 if current_mtime > target_mtime: return True for d in self.parent().collect_dependencies(): build_system = d.build_system() if build_system is None: continue if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date(): return True return False def _is_build_out_of_date(self): current_mtime = self.__get_build_mtime() target_mtime = Cache.get(f'component.{self.name()}.build-mtime') if target_mtime is None: target_mtime = 0 if current_mtime > target_mtime: return True for d in self.parent().collect_dependencies(): build_system = d.build_system() if build_system is None: continue if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date(): return True return False def __get_published_items(self): published = {} for d in self.parent().collect_dependencies(): build_sys = d.build_system() ns = {} if build_sys is not None: ns = build_sys.get_path_namespaces() p = d.publish() if p is None: continue path = p.path() if path is not None: if 'path' not in published: published['path'] = [] path = Path.expand_all(path, relative_to=ns) published['path'].extend(path) cmake = p.cmake() if cmake is not None: if 'cmake' not in published: published['cmake'] = {} if 'module_paths' in cmake: if 'module_paths' not in published['cmake']: published['cmake']['module_paths'] = [] module_paths = cmake['module_paths'] module_paths = Path.expand_all(module_paths, relative_to=ns) published['cmake']['module_paths'].extend(module_paths) return published def configure(self, **kwargs): verbose = kwargs.get('verbose', False) ns = self.get_path_namespaces() stdout = None if not verbose: stdout = subprocess.DEVNULL target = Target.current_target() platform = Platform.current_platform() cmake_platform = platform.get_component('cmake') cmake_args = [ 'cmake', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '--no-warn-unused-cli' ] if 'configure_args' in self.__data: cmake_args.extend(self.__data['configure_args']) published = self.__get_published_items() env = os.environ.copy() if 'path' in published: path = '' if 'PATH' in env: path = env['PATH'] if len(path): path += ':' path += ':'.join(published['path']) env['PATH'] = path module_paths = [] if 'cmake' in published: cmake_publish = published['cmake'] for p in cmake_publish.get('module_paths', []): path = Path(p) for p2 in path.expand_path(relative_to=ns): module_paths.append(p2) install_details = self.parent().install() install_prefix = '' if self.build_for() == 'host': install_prefix = Environment.native_root() cmake_platform = None seed_install_prefix = '/' if install_details is not None: seed_install_prefix = install_details.get('prefix', seed_install_prefix) seed_install_prefix = Path(seed_install_prefix).remove_root().get_path() install_prefix = os.path.join(install_prefix, seed_install_prefix) else: install_prefix = Environment.system_root() env['ROSETTA_SYSROOT'] = Environment.system_root() seed_install_prefix = target.install_prefix() if install_details is not None: seed_install_prefix = install_details.get('prefix', seed_install_prefix) seed_install_prefix = Path(seed_install_prefix).remove_root().get_path() install_prefix = os.path.join(install_prefix, seed_install_prefix) cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={install_prefix}') if cmake_platform is not None: if 'module_paths' in cmake_platform: for p in cmake_platform['module_paths']: module_paths.append(os.path.join(platform.base_directory(), p)) if 'toolchain_file' in cmake_platform: toolchain_file = os.path.join(platform.base_directory(), cmake_platform['toolchain_file']) cmake_args.append(f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}') if 'system_name' in cmake_platform: cmake_args.append(f'-DCMAKE_SYSTEM_NAME={cmake_platform['system_name']}') if 'module_paths' in self.__data: for p in self.__data['module_paths']: path = Path(p) for p2 in path.expand_path(relative_to=ns): module_paths.append(p2) if len(module_paths) > 0: cmake_args.append(f'-DCMAKE_MODULE_PATH={';'.join(module_paths)}') source_dir = os.path.abspath(self.source_dir()) bin_dir = os.path.abspath(self.binary_dir()) cmake_args.append(source_dir) SysPath(bin_dir).mkdir(parents=True, exist_ok=True) result = subprocess.run(cmake_args, cwd=bin_dir, env=env, stdout=stdout) if result.returncode == 0: Cache.set(f'component.{self.name()}.configure-mtime', self.__get_configure_mtime()) else: raise BuildConfigurationError(f'CMake configuration for {self.name()} failed. See log for details') def __find_build_command(self): bin_dir = os.path.abspath(self.binary_dir()) if os.path.isfile(os.path.join(bin_dir, 'build.ninja')): return 'ninja' return None def build(self, **kwargs): verbose = kwargs.get('verbose', False) stdout = None if not verbose: stdout = subprocess.DEVNULL bin_dir = os.path.abspath(self.binary_dir()) build_cmd = self.__find_build_command() if build_cmd is None: raise BuildError(f'Cannot determine which command to use to build {self.name()}') env = os.environ.copy() if self.build_for() != 'host': env['ROSETTA_SYSROOT'] = Environment.system_root() args = [ build_cmd ] result = subprocess.run(args, cwd=bin_dir, stdout=stdout, env=env) if result.returncode != 0: raise BuildConfigurationError(f'{build_cmd} build for {self.name()} failed. See log for details') def install(self, **kwargs): self.install_from_seed() verbose = kwargs.get('verbose', False) stdout = None if not verbose: stdout = subprocess.DEVNULL bin_dir = os.path.abspath(self.binary_dir()) build_cmd = self.__find_build_command() if build_cmd is None: raise BuildError(f'Cannot determine which command to use to build {self.name()}') args = [ build_cmd, 'install' ] result = subprocess.run(args, cwd=bin_dir, stdout=stdout) if result.returncode == 0: Cache.set(f'component.{self.name()}.build-mtime', self.__get_build_mtime())