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
206
207
|
# Copyright 2015 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import enum
import os
import re
import typing as T
from . import ExtensionModule
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):
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'}:
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),
('--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(
'winddows.compile_resoures',
DEPEND_FILES_KW.evolve(since='0.47.0'),
DEPENDS_KW.evolve(since='0.47.0'),
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:
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 indexs 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,
command,
[src],
[output],
depfile=depfile,
depend_files=wrc_depend_files,
extra_depends=wrc_depends,
))
return ModuleReturnValue(res_targets, [res_targets])
def initialize(interp: 'Interpreter') -> WindowsModule:
return WindowsModule(interp)
|