#!/usr/bin/env python3 # vim: set syntax=python ts=4 : # # Copyright (c) 2018 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import re from collections import OrderedDict class CMakeCacheEntry: '''Represents a CMake cache entry. This class understands the type system in a CMakeCache.txt, and converts the following cache types to Python types: Cache Type Python type ---------- ------------------------------------------- FILEPATH str PATH str STRING str OR list of str (if ';' is in the value) BOOL bool INTERNAL str OR list of str (if ';' is in the value) ---------- ------------------------------------------- ''' # Regular expression for a cache entry. # # CMake variable names can include escape characters, allowing a # wider set of names than is easy to match with a regular # expression. To be permissive here, use a non-greedy match up to # the first colon (':'). This breaks if the variable name has a # colon inside, but it's good enough. CACHE_ENTRY = re.compile( r'''(?P.*?) # name :(?PFILEPATH|PATH|STRING|BOOL|INTERNAL) # type =(?P.*) # value ''', re.X) @classmethod def _to_bool(cls, val): # Convert a CMake BOOL string into a Python bool. # # "True if the constant is 1, ON, YES, TRUE, Y, or a # non-zero number. False if the constant is 0, OFF, NO, # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in # the suffix -NOTFOUND. Named boolean constants are # case-insensitive. If the argument is not one of these # constants, it is treated as a variable." # # https://cmake.org/cmake/help/v3.0/command/if.html val = val.upper() if val in ('ON', 'YES', 'TRUE', 'Y'): return 1 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''): return 0 elif val.endswith('-NOTFOUND'): return 0 else: try: v = int(val) return v != 0 except ValueError as exc: raise ValueError('invalid bool {}'.format(val)) from exc @classmethod def from_line(cls, line, line_no): # Comments can only occur at the beginning of a line. # (The value of an entry could contain a comment character). if line.startswith('//') or line.startswith('#'): return None # Whitespace-only lines do not contain cache entries. if not line.strip(): return None m = cls.CACHE_ENTRY.match(line) if not m: return None name, type_, value = (m.group(g) for g in ('name', 'type', 'value')) if type_ == 'BOOL': try: value = cls._to_bool(value) except ValueError as exc: args = exc.args + ('on line {}: {}'.format(line_no, line),) raise ValueError(args) from exc elif type_ in ['STRING', 'INTERNAL']: # If the value is a CMake list (i.e. is a string which # contains a ';'), convert to a Python list. if ';' in value: value = value.split(';') return CMakeCacheEntry(name, value) def __init__(self, name, value): self.name = name self.value = value def __str__(self): fmt = 'CMakeCacheEntry(name={}, value={})' return fmt.format(self.name, self.value) class CMakeCache: '''Parses and represents a CMake cache file.''' @staticmethod def from_file(cache_file): return CMakeCache(cache_file) def __init__(self, cache_file): self.cache_file = cache_file self.load(cache_file) def load(self, cache_file): entries = [] with open(cache_file, 'r') as cache: for line_no, line in enumerate(cache): entry = CMakeCacheEntry.from_line(line, line_no) if entry: entries.append(entry) self._entries = OrderedDict((e.name, e) for e in entries) def get(self, name, default=None): entry = self._entries.get(name) if entry is not None: return entry.value else: return default def get_list(self, name, default=None): if default is None: default = [] entry = self._entries.get(name) if entry is not None: value = entry.value if isinstance(value, list): return value elif isinstance(value, str): return [value] if value else [] else: msg = 'invalid value {} type {}' raise RuntimeError(msg.format(value, type(value))) else: return default def __contains__(self, name): return name in self._entries def __getitem__(self, name): return self._entries[name].value def __setitem__(self, name, entry): if not isinstance(entry, CMakeCacheEntry): msg = 'improper type {} for value {}, expecting CMakeCacheEntry' raise TypeError(msg.format(type(entry), entry)) self._entries[name] = entry def __delitem__(self, name): del self._entries[name] def __iter__(self): return iter(self._entries.values())