blob: 29ae96b0854847fa9cc6cb9d737e52c1788e3c9e [file] [log] [blame]
# 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)