summaryrefslogtreecommitdiffstats
path: root/mesonbuild/modules/windows.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/modules/windows.py')
-rw-r--r--mesonbuild/modules/windows.py212
1 files changed, 212 insertions, 0 deletions
diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py
new file mode 100644
index 0000000..494cfbf
--- /dev/null
+++ b/mesonbuild/modules/windows.py
@@ -0,0 +1,212 @@
+# 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.
+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'}:
+ 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(
+ '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 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,
+ state.environment,
+ 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)