1# Copyright 2019 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from .. import mesonlib 16import typing as T 17 18def parse_generator_expressions(raw: str) -> str: 19 '''Parse CMake generator expressions 20 21 Most generator expressions are simply ignored for 22 simplicety, however some are required for some common 23 use cases. 24 ''' 25 26 # Early abort if no generator expression present 27 if '$<' not in raw: 28 return raw 29 30 out = '' # type: str 31 i = 0 # type: int 32 33 def equal(arg: str) -> str: 34 col_pos = arg.find(',') 35 if col_pos < 0: 36 return '0' 37 else: 38 return '1' if arg[:col_pos] == arg[col_pos + 1:] else '0' 39 40 def vers_comp(op: str, arg: str) -> str: 41 col_pos = arg.find(',') 42 if col_pos < 0: 43 return '0' 44 else: 45 return '1' if mesonlib.version_compare(arg[:col_pos], '{}{}'.format(op, arg[col_pos + 1:])) else '0' 46 47 supported = { 48 # Boolean functions 49 'BOOL': lambda x: '0' if x.upper() in ['0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'] or x.endswith('-NOTFOUND') else '1', 50 'AND': lambda x: '1' if all([y == '1' for y in x.split(',')]) else '0', 51 'OR': lambda x: '1' if any([y == '1' for y in x.split(',')]) else '0', 52 'NOT': lambda x: '0' if x == '1' else '1', 53 54 '0': lambda x: '', 55 '1': lambda x: x, 56 57 # String operations 58 'STREQUAL': equal, 59 'EQUAL': equal, 60 'VERSION_LESS': lambda x: vers_comp('<', x), 61 'VERSION_GREATER': lambda x: vers_comp('>', x), 62 'VERSION_EQUAL': lambda x: vers_comp('=', x), 63 'VERSION_LESS_EQUAL': lambda x: vers_comp('<=', x), 64 'VERSION_GREATER_EQUAL': lambda x: vers_comp('>=', x), 65 66 # String modification 67 'LOWER_CASE': lambda x: x.lower(), 68 'UPPER_CASE': lambda x: x.upper(), 69 70 # Always assume the BUILD_INTERFACE is valid. 71 # INSTALL_INTERFACE is always invalid for subprojects and 72 # it should also never appear in CMake config files, used 73 # for dependencies 74 'INSTALL_INTERFACE': lambda x: '', 75 'BUILD_INTERFACE': lambda x: x, 76 77 # Constants 78 'ANGLE-R': lambda x: '>', 79 'COMMA': lambda x: ',', 80 'SEMICOLON': lambda x: ';', 81 } # type: T.Dict[str, T.Callable[[str], str]] 82 83 # Recursively evaluate generator expressions 84 def eval_generator_expressions() -> str: 85 nonlocal i 86 i += 2 87 88 func = '' # type: str 89 args = '' # type: str 90 res = '' # type: str 91 exp = '' # type: str 92 93 # Determine the body of the expression 94 while i < len(raw): 95 if raw[i] == '>': 96 # End of the generator expression 97 break 98 elif i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<': 99 # Nested generator expression 100 exp += eval_generator_expressions() 101 else: 102 # Generator expression body 103 exp += raw[i] 104 105 i += 1 106 107 # Split the expression into a function and arguments part 108 col_pos = exp.find(':') 109 if col_pos < 0: 110 func = exp 111 else: 112 func = exp[:col_pos] 113 args = exp[col_pos + 1:] 114 115 func = func.strip() 116 args = args.strip() 117 118 # Evaluate the function 119 if func in supported: 120 res = supported[func](args) 121 122 return res 123 124 while i < len(raw): 125 if i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<': 126 # Generator expression detected --> try resolving it 127 out += eval_generator_expressions() 128 else: 129 # Normal string, leave unchanged 130 out += raw[i] 131 132 i += 1 133 134 return out 135