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 collections import namedtuple 16from .. import mesonlib 17from ..mesonlib import listify 18from . import ExtensionModule 19from ..interpreterbase import ( 20 noPosargs, noKwargs, permittedKwargs, 21 InterpreterObject, MutableInterpreterObject, ObjectHolder, 22 InterpreterException, InvalidArguments, InvalidCode, FeatureNew, 23) 24from ..interpreter import ( 25 GeneratedListHolder, CustomTargetHolder, 26 CustomTargetIndexHolder 27) 28 29SourceSetRule = namedtuple('SourceSetRule', 'keys sources if_false sourcesets dependencies extra_deps') 30SourceFiles = namedtuple('SourceFiles', 'sources dependencies') 31 32class SourceSetHolder(MutableInterpreterObject, ObjectHolder): 33 def __init__(self, interpreter): 34 MutableInterpreterObject.__init__(self) 35 ObjectHolder.__init__(self, list()) 36 self.subproject = interpreter.subproject 37 self.environment = interpreter.environment 38 self.subdir = interpreter.subdir 39 self.frozen = False 40 self.methods.update({ 41 'add': self.add_method, 42 'add_all': self.add_all_method, 43 'all_sources': self.all_sources_method, 44 'all_dependencies': self.all_dependencies_method, 45 'apply': self.apply_method, 46 }) 47 48 def check_source_files(self, arg, allow_deps): 49 sources = [] 50 deps = [] 51 for x in arg: 52 if isinstance(x, (str, mesonlib.File, 53 GeneratedListHolder, CustomTargetHolder, 54 CustomTargetIndexHolder)): 55 sources.append(x) 56 elif hasattr(x, 'found'): 57 if not allow_deps: 58 msg = 'Dependencies are not allowed in the if_false argument.' 59 raise InvalidArguments(msg) 60 deps.append(x) 61 else: 62 msg = 'Sources must be strings or file-like objects.' 63 raise InvalidArguments(msg) 64 mesonlib.check_direntry_issues(sources) 65 return sources, deps 66 67 def check_conditions(self, arg): 68 keys = [] 69 deps = [] 70 for x in listify(arg): 71 if isinstance(x, str): 72 keys.append(x) 73 elif hasattr(x, 'found'): 74 deps.append(x) 75 else: 76 raise InvalidArguments('Conditions must be strings or dependency object') 77 return keys, deps 78 79 @permittedKwargs(['when', 'if_false', 'if_true']) 80 def add_method(self, args, kwargs): 81 if self.frozen: 82 raise InvalidCode('Tried to use \'add\' after querying the source set') 83 when = listify(kwargs.get('when', [])) 84 if_true = listify(kwargs.get('if_true', [])) 85 if_false = listify(kwargs.get('if_false', [])) 86 if not when and not if_true and not if_false: 87 if_true = args 88 elif args: 89 raise InterpreterException('add called with both positional and keyword arguments') 90 keys, dependencies = self.check_conditions(when) 91 sources, extra_deps = self.check_source_files(if_true, True) 92 if_false, _ = self.check_source_files(if_false, False) 93 self.held_object.append(SourceSetRule(keys, sources, if_false, [], dependencies, extra_deps)) 94 95 @permittedKwargs(['when', 'if_true']) 96 def add_all_method(self, args, kwargs): 97 if self.frozen: 98 raise InvalidCode('Tried to use \'add_all\' after querying the source set') 99 when = listify(kwargs.get('when', [])) 100 if_true = listify(kwargs.get('if_true', [])) 101 if not when and not if_true: 102 if_true = args 103 elif args: 104 raise InterpreterException('add_all called with both positional and keyword arguments') 105 keys, dependencies = self.check_conditions(when) 106 for s in if_true: 107 if not isinstance(s, SourceSetHolder): 108 raise InvalidCode('Arguments to \'add_all\' after the first must be source sets') 109 s.frozen = True 110 self.held_object.append(SourceSetRule(keys, [], [], if_true, dependencies, [])) 111 112 def collect(self, enabled_fn, all_sources, into=None): 113 if not into: 114 into = SourceFiles(set(), set()) 115 for entry in self.held_object: 116 if all(x.found() for x in entry.dependencies) and \ 117 all(enabled_fn(key) for key in entry.keys): 118 into.sources.update(entry.sources) 119 into.dependencies.update(entry.dependencies) 120 into.dependencies.update(entry.extra_deps) 121 for ss in entry.sourcesets: 122 ss.collect(enabled_fn, all_sources, into) 123 if not all_sources: 124 continue 125 into.sources.update(entry.if_false) 126 return into 127 128 @noKwargs 129 @noPosargs 130 def all_sources_method(self, args, kwargs): 131 self.frozen = True 132 files = self.collect(lambda x: True, True) 133 return list(files.sources) 134 135 @noKwargs 136 @noPosargs 137 @FeatureNew('source_set.all_dependencies() method', '0.52.0') 138 def all_dependencies_method(self, args, kwargs): 139 self.frozen = True 140 files = self.collect(lambda x: True, True) 141 return list(files.dependencies) 142 143 @permittedKwargs(['strict']) 144 def apply_method(self, args, kwargs): 145 if len(args) != 1: 146 raise InterpreterException('Apply takes exactly one argument') 147 config_data = args[0] 148 self.frozen = True 149 strict = kwargs.get('strict', True) 150 if isinstance(config_data, dict): 151 def _get_from_config_data(key): 152 if strict and key not in config_data: 153 raise InterpreterException('Entry {} not in configuration dictionary.'.format(key)) 154 return config_data.get(key, False) 155 else: 156 config_cache = dict() 157 158 def _get_from_config_data(key): 159 nonlocal config_cache 160 if key not in config_cache: 161 args = [key] if strict else [key, False] 162 config_cache[key] = config_data.get_method(args, {}) 163 return config_cache[key] 164 165 files = self.collect(_get_from_config_data, False) 166 res = SourceFilesHolder(files) 167 return res 168 169class SourceFilesHolder(InterpreterObject, ObjectHolder): 170 def __init__(self, files): 171 InterpreterObject.__init__(self) 172 ObjectHolder.__init__(self, files) 173 self.methods.update({ 174 'sources': self.sources_method, 175 'dependencies': self.dependencies_method, 176 }) 177 178 @noPosargs 179 @noKwargs 180 def sources_method(self, args, kwargs): 181 return list(self.held_object.sources) 182 183 @noPosargs 184 @noKwargs 185 def dependencies_method(self, args, kwargs): 186 return list(self.held_object.dependencies) 187 188class SourceSetModule(ExtensionModule): 189 @FeatureNew('SourceSet module', '0.51.0') 190 def __init__(self, *args, **kwargs): 191 super().__init__(*args, **kwargs) 192 self.snippets.add('source_set') 193 194 @noKwargs 195 @noPosargs 196 def source_set(self, interpreter, state, args, kwargs): 197 return SourceSetHolder(interpreter) 198 199def initialize(*args, **kwargs): 200 return SourceSetModule(*args, **kwargs) 201