Merge pull request #9375 from dcbaker/submit/windows-module-typing

Typing for the Windows module
This commit is contained in:
Jussi Pakkanen 2021-10-29 22:45:04 +03:00 committed by GitHub
commit af85738daf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 56 deletions

View File

@ -7,10 +7,22 @@ Windows.
### compile_resources ### compile_resources
```
windows.compile_resources(...(string | File | CustomTarget | CustomTargetIndex),
args: []string,
depend_files: [](string | File),
depends: [](BuildTarget | CustomTarget)
include_directories: [](IncludeDirectories | string)): []CustomTarget
```
Compiles Windows `rc` files specified in the positional arguments. Compiles Windows `rc` files specified in the positional arguments.
Returns an opaque object that you put in the list of sources for the Returns a list of `CustomTarget` objects that you put in the list of sources for
target you want to have the resources in. This method has the the target you want to have the resources in.
following keyword argument.
*Since 0.61.0* CustomTargetIndexs and CustomTargets with more than out output
*may be used as positional arguments.
This method has the following keyword arguments:
- `args` lists extra arguments to pass to the resource compiler - `args` lists extra arguments to pass to the resource compiler
- `depend_files` lists resource files that the resource script depends on - `depend_files` lists resource files that the resource script depends on

View File

@ -0,0 +1,22 @@
## Windows.compile_resources CustomTarget
Previously the Windows module only accepted CustomTargets with one output, it
now accepts them with more than one output, and creates a windows resource
target for each output. Additionally it now accepts indexes of CustomTargets
```meson
ct = custom_target(
'multiple',
output : ['resource', 'another resource'],
...
)
ct2 = custom_target(
'slice',
output : ['resource', 'not a resource'],
...
)
resources = windows.compile_resources(ct, ct2[0])
```

View File

@ -2700,7 +2700,7 @@ class CustomTargetIndex(HoldableObject):
def get_filename(self) -> str: def get_filename(self) -> str:
return self.output return self.output
def get_id(self): def get_id(self) -> str:
return self.target.get_id() return self.target.get_id()
def get_all_link_deps(self): def get_all_link_deps(self):

View File

@ -6,7 +6,7 @@
import typing as T import typing as T
from .. import compilers from .. import compilers
from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList from ..build import EnvironmentVariables, CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs
from ..coredata import UserFeatureOption from ..coredata import UserFeatureOption
from ..interpreterbase import TYPE_var from ..interpreterbase import TYPE_var
from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo
@ -278,3 +278,10 @@ CT_INSTALL_DIR_KW: KwargInfo[T.List[T.Union[str, bool]]] = KwargInfo(
) )
CT_BUILD_BY_DEFAULT: KwargInfo[T.Optional[bool]] = KwargInfo('build_by_default', (bool, type(None)), since='0.40.0') CT_BUILD_BY_DEFAULT: KwargInfo[T.Optional[bool]] = KwargInfo('build_by_default', (bool, type(None)), since='0.40.0')
INCLUDE_DIRECTORIES: KwargInfo[T.List[T.Union[str, IncludeDirs]]] = KwargInfo(
'include_dirs',
ContainerTypeInfo(list, (str, IncludeDirs)),
listify=True,
default=[],
)

View File

@ -15,40 +15,58 @@
import enum import enum
import os import os
import re import re
import typing as T
from .. import mlog
from .. import mesonlib, build
from ..mesonlib import MachineChoice, MesonException, extract_as_list
from . import ModuleReturnValue
from . import ExtensionModule from . import ExtensionModule
from ..interpreterbase import permittedKwargs, FeatureNewKwargs, flatten from . import ModuleReturnValue
from .. import mesonlib, build
from .. import mlog
from ..interpreter.type_checking import DEPEND_FILES_KW, DEPENDS_KW, INCLUDE_DIRECTORIES
from ..interpreterbase.decorators import ContainerTypeInfo, FeatureNew, KwargInfo, typed_kwargs, typed_pos_args
from ..mesonlib import MachineChoice, MesonException
from ..programs import ExternalProgram from ..programs import ExternalProgram
if T.TYPE_CHECKING:
from . import ModuleState
from ..compilers import Compiler
from ..interpreter import Interpreter
from typing_extensions import TypedDict
class CompileResources(TypedDict):
depend_files: T.List[mesonlib.FileOrString]
depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]]
include_directories: T.List[T.Union[str, build.IncludeDirs]]
args: T.List[str]
class ResourceCompilerType(enum.Enum): class ResourceCompilerType(enum.Enum):
windres = 1 windres = 1
rc = 2 rc = 2
wrc = 3 wrc = 3
class WindowsModule(ExtensionModule): class WindowsModule(ExtensionModule):
def __init__(self, interpreter): def __init__(self, interpreter: 'Interpreter'):
super().__init__(interpreter) super().__init__(interpreter)
self._rescomp: T.Optional[T.Tuple[ExternalProgram, ResourceCompilerType]] = None
self.methods.update({ self.methods.update({
'compile_resources': self.compile_resources, 'compile_resources': self.compile_resources,
}) })
def detect_compiler(self, compilers): def detect_compiler(self, compilers: T.Dict[str, 'Compiler']) -> 'Compiler':
for l in ('c', 'cpp'): for l in ('c', 'cpp'):
if l in compilers: if l in compilers:
return compilers[l] return compilers[l]
raise MesonException('Resource compilation requires a C or C++ compiler.') raise MesonException('Resource compilation requires a C or C++ compiler.')
def _find_resource_compiler(self, state): def _find_resource_compiler(self, state: 'ModuleState') -> T.Tuple[ExternalProgram, ResourceCompilerType]:
# FIXME: Does not handle `native: true` executables, see # FIXME: Does not handle `native: true` executables, see
# See https://github.com/mesonbuild/meson/issues/1531 # See https://github.com/mesonbuild/meson/issues/1531
# Take a parameter instead of the hardcoded definition below # Take a parameter instead of the hardcoded definition below
for_machine = MachineChoice.HOST for_machine = MachineChoice.HOST
if hasattr(self, '_rescomp'): if self._rescomp:
return self._rescomp return self._rescomp
# Will try cross / native file and then env var # Will try cross / native file and then env var
@ -80,22 +98,26 @@ class WindowsModule(ExtensionModule):
return self._rescomp return self._rescomp
@FeatureNewKwargs('windows.compile_resources', '0.47.0', ['depend_files', 'depends']) @typed_pos_args('windows.compile_resources', varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex), min_varargs=1)
@permittedKwargs({'args', 'include_directories', 'depend_files', 'depends'}) @typed_kwargs(
def compile_resources(self, state, args, kwargs): 'winddows.compile_resoures',
extra_args = mesonlib.stringlistify(flatten(kwargs.get('args', []))) DEPEND_FILES_KW.evolve(since='0.47.0'),
wrc_depend_files = extract_as_list(kwargs, 'depend_files', pop = True) DEPENDS_KW.evolve(since='0.47.0'),
wrc_depends = extract_as_list(kwargs, 'depends', pop = True) INCLUDE_DIRECTORIES.evolve(name='include_directories'),
KwargInfo('args', ContainerTypeInfo(list, str), default=[], listify=True),
)
def compile_resources(self, state: 'ModuleState',
args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]],
kwargs: 'CompileResources') -> ModuleReturnValue:
extra_args = kwargs['args'].copy()
wrc_depend_files = kwargs['depend_files']
wrc_depends = kwargs['depends']
for d in wrc_depends: for d in wrc_depends:
if isinstance(d, build.CustomTarget): if isinstance(d, build.CustomTarget):
extra_args += state.get_include_args([ extra_args += state.get_include_args([
build.IncludeDirs('', [], False, [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(d))]) build.IncludeDirs('', [], False, [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(d))])
]) ])
inc_dirs = extract_as_list(kwargs, 'include_directories', pop = True) extra_args += state.get_include_args(kwargs['include_directories'])
for incd in inc_dirs:
if not isinstance(incd, (str, build.IncludeDirs)):
raise MesonException('Resource include dirs should be include_directories().')
extra_args += state.get_include_args(inc_dirs)
rescomp, rescomp_type = self._find_resource_compiler(state) rescomp, rescomp_type = self._find_resource_compiler(state)
if rescomp_type == ResourceCompilerType.rc: if rescomp_type == ResourceCompilerType.rc:
@ -119,53 +141,56 @@ class WindowsModule(ExtensionModule):
suffix = 'o' suffix = 'o'
res_args = extra_args + ['@INPUT@', '-o', '@OUTPUT@'] res_args = extra_args + ['@INPUT@', '-o', '@OUTPUT@']
res_targets = [] res_targets: T.List[build.CustomTarget] = []
def add_target(src): def get_names() -> T.Iterable[T.Tuple[str, str, T.Union[str, mesonlib.File, build.CustomTargetIndex]]]:
if isinstance(src, list): for src in args[0]:
for subsrc in src: if isinstance(src, str):
add_target(subsrc) yield os.path.join(state.subdir, src), src, src
return elif isinstance(src, mesonlib.File):
yield src.relative_name(), src.fname, src
if isinstance(src, str): elif isinstance(src, build.CustomTargetIndex):
name_formatted = src FeatureNew.single_use('windows.compile_resource CustomTargetIndex in positional arguments', '0.61.0', state.subproject)
name = os.path.join(state.subdir, src) # This dance avoids a case where two indexs of the same
elif isinstance(src, mesonlib.File): # target are given as separate arguments.
name_formatted = src.fname yield (f'{src.get_id()}_{src.target.get_outputs().index(src.output)}',
name = src.relative_name() f'windows_compile_resources_{src.get_filename()}', src)
elif isinstance(src, build.CustomTarget): else:
if len(src.get_outputs()) > 1: if len(src.get_outputs()) > 1:
raise MesonException('windows.compile_resources does not accept custom targets with more than 1 output.') FeatureNew.single_use('windows.compile_resource CustomTarget with multiple outputs in positional arguments', '0.61.0', state.subproject)
for i, out in enumerate(src.get_outputs()):
# Chances are that src.get_filename() is already the name of that # Chances are that src.get_filename() is already the name of that
# target, add a prefix to avoid name clash. # target, add a prefix to avoid name clash.
name_formatted = 'windows_compile_resources_' + src.get_filename() yield f'{src.get_id()}_{i}', f'windows_compile_resources_{i}_{out}', src[i]
name = src.get_id()
else:
raise MesonException(f'Unexpected source type {src!r}. windows.compile_resources accepts only strings, files, custom targets, and lists thereof.')
for name, name_formatted, src in get_names():
# Path separators are not allowed in target names # Path separators are not allowed in target names
name = name.replace('/', '_').replace('\\', '_').replace(':', '_') name = name.replace('/', '_').replace('\\', '_').replace(':', '_')
name_formatted = name_formatted.replace('/', '_').replace('\\', '_').replace(':', '_') name_formatted = name_formatted.replace('/', '_').replace('\\', '_').replace(':', '_')
output = f'{name}_@BASENAME@.{suffix}'
command: T.List[T.Union[str, ExternalProgram]] = []
command.append(rescomp)
command.extend(res_args)
res_kwargs = { res_kwargs = {
'output': name + '_@BASENAME@.' + suffix, 'output': output,
'input': [src], 'input': [src],
'command': [rescomp] + res_args,
'depend_files': wrc_depend_files, 'depend_files': wrc_depend_files,
'depends': wrc_depends, 'depends': wrc_depends,
} }
# instruct binutils windres to generate a preprocessor depfile # instruct binutils windres to generate a preprocessor depfile
if rescomp_type == ResourceCompilerType.windres: if rescomp_type == ResourceCompilerType.windres:
res_kwargs['depfile'] = res_kwargs['output'] + '.d' res_kwargs['depfile'] = f'{output}.d'
res_kwargs['command'] += ['--preprocessor-arg=-MD', '--preprocessor-arg=-MQ@OUTPUT@', '--preprocessor-arg=-MF@DEPFILE@'] command.extend(['--preprocessor-arg=-MD',
'--preprocessor-arg=-MQ@OUTPUT@',
'--preprocessor-arg=-MF@DEPFILE@'])
res_kwargs['command'] = command
res_targets.append(build.CustomTarget(name_formatted, state.subdir, state.subproject, res_kwargs)) res_targets.append(build.CustomTarget(name_formatted, state.subdir, state.subproject, res_kwargs))
add_target(args)
return ModuleReturnValue(res_targets, [res_targets]) return ModuleReturnValue(res_targets, [res_targets])
def initialize(*args, **kwargs): def initialize(interp: 'Interpreter') -> WindowsModule:
return WindowsModule(*args, **kwargs) return WindowsModule(interp)

View File

@ -44,6 +44,7 @@ modules = [
'mesonbuild/modules/qt.py', 'mesonbuild/modules/qt.py',
'mesonbuild/modules/unstable_external_project.py', 'mesonbuild/modules/unstable_external_project.py',
'mesonbuild/modules/unstable_rust.py', 'mesonbuild/modules/unstable_rust.py',
'mesonbuild/modules/windows.py',
'mesonbuild/mparser.py', 'mesonbuild/mparser.py',
'mesonbuild/msetup.py', 'mesonbuild/msetup.py',
'mesonbuild/mtest.py', 'mesonbuild/mtest.py',