| # 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) |