aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/build.py
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2022-01-19 12:42:25 -0800
committerEli Schwartz <eschwartz93@gmail.com>2022-01-28 15:53:20 -0500
commit11f96380351a88059ec55f1070fdebc1b1033117 (patch)
tree3577f52920c3e2159f193605a894cb090b95ec10 /mesonbuild/build.py
parentb402817fb6f0392812bfa272bdbc05c9c30139fa (diff)
downloadmeson-11f96380351a88059ec55f1070fdebc1b1033117.zip
meson-11f96380351a88059ec55f1070fdebc1b1033117.tar.gz
meson-11f96380351a88059ec55f1070fdebc1b1033117.tar.bz2
build: replace kwargs in CustomTarget initializer
Because we don't want to pass the Interpreter kwargs into the build layer. This turned out to be a mega commit, as there's really on elegant way to make this change in an incremental way. On the nice side, mypy made this change super easy, as nearly all of the calls to `CustomTarget` are fully type checked! It also turns out that we're not handling install_tags in custom_target correctly, since we're not converting the boolean values into Optional values!
Diffstat (limited to 'mesonbuild/build.py')
-rw-r--r--mesonbuild/build.py214
1 files changed, 67 insertions, 147 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index a392f98..3fa647a 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
from collections import OrderedDict
from dataclasses import dataclass, field
from functools import lru_cache
@@ -24,6 +25,7 @@ import re
import textwrap
import typing as T
+
from . import environment
from . import dependencies
from . import mlog
@@ -2328,47 +2330,78 @@ class CommandBase:
return final_cmd
class CustomTarget(Target, CommandBase):
- known_kwargs = {
- 'input',
- 'output',
- 'command',
- 'capture',
- 'feed',
- 'install',
- 'install_dir',
- 'install_mode',
- 'install_tag',
- 'build_always',
- 'build_always_stale',
- 'depends',
- 'depend_files',
- 'depfile',
- 'build_by_default',
- 'override_options',
- 'console',
- 'env',
- }
- install_dir: T.List[T.Union[str, bool]]
+ typename = 'custom'
- def __init__(self, name: str, subdir: str, subproject: str, kwargs: T.Mapping[str, T.Any],
- absolute_paths: bool = False, backend: T.Optional['Backend'] = None):
- self.typename = 'custom'
+ def __init__(self,
+ name: T.Optional[str],
+ subdir: str,
+ subproject: str,
+ command: T.Sequence[T.Union[
+ str, BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList, programs.ExternalProgram, File]],
+ sources: T.Sequence[T.Union[
+ str, File, BuildTarget, CustomTarget, CustomTargetIndex,
+ ExtractedObjects, GeneratedList, programs.ExternalProgram]],
+ outputs: T.List[str],
+ *,
+ build_always_stale: bool = False,
+ build_by_default: T.Optional[bool] = None,
+ capture: bool = False,
+ console: bool = False,
+ depend_files: T.Optional[T.Sequence[FileOrString]] = None,
+ extra_depends: T.Optional[T.Sequence[T.Union[str, SourceOutputs]]] = None,
+ depfile: T.Optional[str] = None,
+ env: T.Optional[EnvironmentVariables] = None,
+ feed: bool = False,
+ install: bool = False,
+ install_dir: T.Optional[T.Sequence[T.Union[str, bool]]] = None,
+ install_mode: T.Optional[FileMode] = None,
+ install_tag: T.Optional[T.Sequence[T.Optional[str]]] = None,
+ override_options: T.Optional[T.Dict[OptionKey, str]] = None,
+ absolute_paths: bool = False,
+ backend: T.Optional['Backend'] = None,
+ ):
# TODO expose keyword arg to make MachineChoice.HOST configurable
super().__init__(name, subdir, subproject, False, MachineChoice.HOST)
+ self.sources = list(sources)
+ self.outputs = substitute_values(
+ outputs, get_filenames_templates_dict(
+ get_sources_string_names(sources, backend),
+ []))
+ self.build_by_default = build_by_default if build_by_default is not None else install
+ self.build_always_stale = build_always_stale
+ self.capture = capture
+ self.console = console
+ self.depend_files = list(depend_files or [])
self.dependencies: T.List[T.Union[CustomTarget, BuildTarget]] = []
- self.extra_depends: T.List[T.Union[CustomTarget, BuildTarget]] = []
- self.depend_files = [] # Files that this target depends on but are not on the command line.
- self.depfile = None
- self.process_kwargs(kwargs, backend)
+ # must be after depend_files and dependencies
+ self.command = self.flatten_command(command)
+ self.depfile = depfile
+ self.env = env or EnvironmentVariables()
+ self.extra_depends = list(extra_depends or [])
+ self.feed = feed
+ self.install = install
+ self.install_dir = list(install_dir or [])
+ self.install_mode = install_mode
+ _install_tag: T.List[T.Optional[str]]
+ if not install_tag:
+ _install_tag = [None] * len(self.outputs)
+ elif len(install_tag) == 1:
+ _install_tag = list(install_tag) * len(self.outputs)
+ else:
+ _install_tag = list(install_tag)
+ self.install_tag = _install_tag
+ self.name = name if name else self.outputs[0]
+
+ if override_options:
+ for k, v in override_options.items():
+ if k.lang:
+ self.option_overrides_compiler[k.evolve(machine=self.for_machine)] = v
+ else:
+ self.option_overrides_base[k] = v
+
# Whether to use absolute paths for all files on the commandline
self.absolute_paths = absolute_paths
- unknowns = []
- for k in kwargs:
- if k not in CustomTarget.known_kwargs:
- unknowns.append(k)
- if unknowns:
- mlog.warning('Unknown keyword arguments in target {}: {}'.format(self.name, ', '.join(unknowns)))
def get_default_install_dir(self, environment) -> T.Tuple[str, str]:
return None, None
@@ -2405,119 +2438,6 @@ class CustomTarget(Target, CommandBase):
bdeps.update(d.get_transitive_build_target_deps())
return bdeps
- def process_kwargs(self, kwargs, backend):
- self.process_kwargs_base(kwargs)
- self.sources = extract_as_list(kwargs, 'input')
- if 'output' not in kwargs:
- raise InvalidArguments('Missing keyword argument "output".')
- self.outputs = listify(kwargs['output'])
- # This will substitute values from the input into output and return it.
- inputs = get_sources_string_names(self.sources, backend)
- values = get_filenames_templates_dict(inputs, [])
- for i in self.outputs:
- if not isinstance(i, str):
- raise InvalidArguments('Output argument not a string.')
- if i == '':
- raise InvalidArguments('Output must not be empty.')
- if i.strip() == '':
- raise InvalidArguments('Output must not consist only of whitespace.')
- if has_path_sep(i):
- raise InvalidArguments(f'Output {i!r} must not contain a path segment.')
- if '@INPUT@' in i or '@INPUT0@' in i:
- m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \
- 'mean @PLAINNAME@ or @BASENAME@?'
- raise InvalidArguments(m)
- # We already check this during substitution, but the error message
- # will be unclear/confusing, so check it here.
- if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i):
- m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \
- "there is more than one input (we can't know which to use)"
- raise InvalidArguments(m)
- self.outputs = substitute_values(self.outputs, values)
- if not self.name:
- self.name = self.outputs[0]
- self.capture = kwargs.get('capture', False)
- if self.capture and len(self.outputs) != 1:
- raise InvalidArguments('Capturing can only output to a single file.')
- self.feed = kwargs.get('feed', False)
- if self.feed and len(self.sources) != 1:
- raise InvalidArguments('Feeding can only input from a single file.')
- self.console = kwargs.get('console', False)
- if not isinstance(self.console, bool):
- raise InvalidArguments('"console" kwarg only accepts booleans')
- if self.capture and self.console:
- raise InvalidArguments("Can't both capture output and output to console")
- if 'command' not in kwargs:
- raise InvalidArguments('Missing keyword argument "command".')
- if kwargs.get('depfile') is not None:
- depfile = kwargs['depfile']
- if not isinstance(depfile, str):
- raise InvalidArguments('Depfile must be a string.')
- if os.path.basename(depfile) != depfile:
- raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
- self.depfile = depfile
- self.command = self.flatten_command(kwargs['command'])
- for c in self.command:
- if self.capture and isinstance(c, str) and '@OUTPUT@' in c:
- raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.')
- if self.feed and isinstance(c, str) and '@INPUT@' in c:
- raise InvalidArguments('@INPUT@ is not allowed when feeding input.')
- if 'install' in kwargs:
- self.install = kwargs['install']
- if not isinstance(self.install, bool):
- raise InvalidArguments('"install" must be boolean.')
- if self.install:
- if not kwargs.get('install_dir', False):
- raise InvalidArguments('"install_dir" must be specified '
- 'when installing a target')
-
- if isinstance(kwargs['install_dir'], list):
- FeatureNew.single_use('multiple install_dir for custom_target', '0.40.0', self.subproject)
- # If an item in this list is False, the output corresponding to
- # the list index of that item will not be installed
- self.install_dir = typeslistify(kwargs['install_dir'], (str, bool))
- self.install_mode = kwargs.get('install_mode', None)
- # If only one tag is provided, assume all outputs have the same tag.
- # Otherwise, we must have as much tags as outputs.
- install_tag: T.List[T.Union[str, bool, None]] = typeslistify(kwargs.get('install_tag', []), (str, bool, type(None)))
- if not install_tag:
- self.install_tag = [None] * len(self.outputs)
- elif len(install_tag) == 1:
- self.install_tag = install_tag * len(self.outputs)
- elif install_tag and len(install_tag) != len(self.outputs):
- m = f'Target {self.name!r} has {len(self.outputs)} outputs but {len(install_tag)} "install_tag"s were found.'
- raise InvalidArguments(m)
- else:
- self.install_tag = install_tag
- else:
- self.install = False
- self.install_dir = []
- self.install_mode = None
- self.install_tag = []
- if kwargs.get('build_always') is not None and kwargs.get('build_always_stale') is not None:
- raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.')
- elif kwargs.get('build_always') is not None:
- if kwargs.get('build_by_default') is not None:
- self.build_by_default = kwargs['build_always']
- self.build_always_stale = kwargs['build_always']
- elif kwargs.get('build_always_stale') is not None:
- self.build_always_stale = kwargs['build_always_stale']
- if not isinstance(self.build_always_stale, bool):
- raise InvalidArguments('Argument build_always_stale must be a boolean.')
- extra_deps, depend_files = (extract_as_list(kwargs, c, pop=False) for c in ['depends', 'depend_files'])
- for ed in extra_deps:
- if not isinstance(ed, (CustomTarget, BuildTarget)):
- raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target '
- f'(executable or a library) got: {type(ed)}({ed})')
- self.extra_depends.append(ed)
- for i in depend_files:
- if isinstance(i, (File, str)):
- self.depend_files.append(i)
- else:
- mlog.debug(i)
- raise InvalidArguments(f'Unknown type {type(i).__name__!r} in depend_files.')
- self.env = kwargs.get('env')
-
def get_dependencies(self):
return self.dependencies