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