aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/modules/windows.py
blob: 29ae96b0854847fa9cc6cb9d737e52c1788e3c9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# SPDX-License-Identifier: Apache-2.0
# Copyright 2015 The Meson development team

from __future__ import annotations

import enum
import os
import re
import typing as T


from . import ExtensionModule, ModuleInfo
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

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 RcKwargs(TypedDict):
        output: str
        input: T.List[T.Union[mesonlib.FileOrString, build.CustomTargetIndex]]
        depfile: T.Optional[str]
        depend_files: T.List[mesonlib.FileOrString]
        depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]]
        command: T.List[T.Union[str, ExternalProgram]]

class ResourceCompilerType(enum.Enum):
    windres = 1
    rc = 2
    wrc = 3

class WindowsModule(ExtensionModule):

    INFO = ModuleInfo('windows')

    def __init__(self, interpreter: 'Interpreter'):
        super().__init__(interpreter)
        self._rescomp: T.Optional[T.Tuple[ExternalProgram, ResourceCompilerType]] = None
        self.methods.update({
            'compile_resources': self.compile_resources,
        })

    def detect_compiler(self, compilers: T.Dict[str, 'Compiler']) -> 'Compiler':
        for l in ('c', 'cpp'):
            if l in compilers:
                return compilers[l]
        raise MesonException('Resource compilation requires a C or C++ compiler.')

    def _find_resource_compiler(self, state: 'ModuleState') -> T.Tuple[ExternalProgram, ResourceCompilerType]:
        # FIXME: Does not handle `native: true` executables, see
        # See https://github.com/mesonbuild/meson/issues/1531
        # Take a parameter instead of the hardcoded definition below
        for_machine = MachineChoice.HOST

        if self._rescomp:
            return self._rescomp

        # Will try cross / native file and then env var
        rescomp = ExternalProgram.from_bin_list(state.environment, for_machine, 'windres')

        if not rescomp or not rescomp.found():
            comp = self.detect_compiler(state.environment.coredata.compilers[for_machine])
            if comp.id in {'msvc', 'clang-cl', 'intel-cl'} or (comp.linker and comp.linker.id in {'link', 'lld-link'}):
                # Microsoft compilers uses rc irrespective of the frontend
                rescomp = ExternalProgram('rc', silent=True)
            else:
                rescomp = ExternalProgram('windres', silent=True)

        if not rescomp.found():
            raise MesonException('Could not find Windows resource compiler')

        for (arg, match, rc_type) in [
                ('/?', '^.*Microsoft.*Resource Compiler.*$', ResourceCompilerType.rc),
                ('/?', 'LLVM Resource Converter.*$', ResourceCompilerType.rc),
                ('--version', '^.*GNU windres.*$', ResourceCompilerType.windres),
                ('--version', '^.*Wine Resource Compiler.*$', ResourceCompilerType.wrc),
        ]:
            p, o, e = mesonlib.Popen_safe(rescomp.get_command() + [arg])
            m = re.search(match, o, re.MULTILINE)
            if m:
                mlog.log('Windows resource compiler: %s' % m.group())
                self._rescomp = (rescomp, rc_type)
                break
        else:
            raise MesonException('Could not determine type of Windows resource compiler')

        return self._rescomp

    @typed_pos_args('windows.compile_resources', varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex), min_varargs=1)
    @typed_kwargs(
        'windows.compile_resources',
        DEPEND_FILES_KW.evolve(since='0.47.0'),
        DEPENDS_KW.evolve(since='0.47.0'),
        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:
            if isinstance(d, build.CustomTarget):
                extra_args += state.get_include_args([
                    build.IncludeDirs('', [], False, [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(d))])
                ])
        extra_args += state.get_include_args(kwargs['include_directories'])

        rescomp, rescomp_type = self._find_resource_compiler(state)
        if rescomp_type == ResourceCompilerType.rc:
            # RC is used to generate .res files, a special binary resource
            # format, which can be passed directly to LINK (apparently LINK uses
            # CVTRES internally to convert this to a COFF object)
            suffix = 'res'
            res_args = extra_args + ['/nologo', '/fo@OUTPUT@', '@INPUT@']
        elif rescomp_type == ResourceCompilerType.windres:
            # ld only supports object files, so windres is used to generate a
            # COFF object
            suffix = 'o'
            res_args = extra_args + ['@INPUT@', '@OUTPUT@']

            m = 'Argument {!r} has a space which may not work with windres due to ' \
                'a MinGW bug: https://sourceware.org/bugzilla/show_bug.cgi?id=4933'
            for arg in extra_args:
                if ' ' in arg:
                    mlog.warning(m.format(arg), fatal=False)
        else:
            suffix = 'o'
            res_args = extra_args + ['@INPUT@', '-o', '@OUTPUT@']

        res_targets: T.List[build.CustomTarget] = []

        def get_names() -> T.Iterable[T.Tuple[str, str, T.Union[str, mesonlib.File, build.CustomTargetIndex]]]:
            for src in args[0]:
                if isinstance(src, str):
                    yield os.path.join(state.subdir, src), src, src
                elif isinstance(src, mesonlib.File):
                    yield src.relative_name(), src.fname, src
                elif isinstance(src, build.CustomTargetIndex):
                    FeatureNew.single_use('windows.compile_resource CustomTargetIndex in positional arguments', '0.61.0',
                                          state.subproject, location=state.current_node)
                    # This dance avoids a case where two indexes of the same
                    # target are given as separate arguments.
                    yield (f'{src.get_id()}_{src.target.get_outputs().index(src.output)}',
                           f'windows_compile_resources_{src.get_filename()}', src)
                else:
                    if len(src.get_outputs()) > 1:
                        FeatureNew.single_use('windows.compile_resource CustomTarget with multiple outputs in positional arguments',
                                              '0.61.0', state.subproject, location=state.current_node)
                    for i, out in enumerate(src.get_outputs()):
                        # Chances are that src.get_filename() is already the name of that
                        # target, add a prefix to avoid name clash.
                        yield f'{src.get_id()}_{i}', f'windows_compile_resources_{i}_{out}', src[i]

        for name, name_formatted, src in get_names():
            # Path separators are not allowed in target names
            name = name.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)
            depfile: T.Optional[str] = None
            # instruct binutils windres to generate a preprocessor depfile
            if rescomp_type == ResourceCompilerType.windres:
                depfile = f'{output}.d'
                command.extend(['--preprocessor-arg=-MD',
                                '--preprocessor-arg=-MQ@OUTPUT@',
                                '--preprocessor-arg=-MF@DEPFILE@'])

            res_targets.append(build.CustomTarget(
                name_formatted,
                state.subdir,
                state.subproject,
                state.environment,
                command,
                [src],
                [output],
                depfile=depfile,
                depend_files=wrc_depend_files,
                extra_depends=wrc_depends,
                description='Compiling Windows resource {}',
            ))

        return ModuleReturnValue(res_targets, [res_targets])

def initialize(interp: 'Interpreter') -> WindowsModule:
    return WindowsModule(interp)