The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
8.8 KiB

# 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 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)
# 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)
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)
res_kwargs = {
'output': output,
'input': [src],
'depend_files': wrc_depend_files,
'depends': wrc_depends,
}
# instruct binutils windres to generate a preprocessor depfile
if rescomp_type == ResourceCompilerType.windres:
res_kwargs['depfile'] = f'{output}.d'
command.extend(['--preprocessor-arg=-MD',
'--preprocessor-arg=-MQ@OUTPUT@',
'--preprocessor-arg=-MF@DEPFILE@'])
res_kwargs['command'] = command
windows: reduce chance of going over path limit in backend/vs When building with vs2019 (not ninja), a path length error will be thrown if the path to a resource file is even remotely deep within the tree. This is largely because the target name includes the string "Windows resource for file 'full path'", which is then expanded twice (once for the .vcxproj itself, and once for IntDir) and added to the full path. When combined with the tiny path limits on Windows, it is easy to exceed path limits. This error is largely avoided by the ninja back-end. Unlike the vs back-end, the ninja back-end does not use target.get_id() as part of the project file path, nor does it use target.get_id() as part of get_target_private_dir(). Example error: error MSB4184: The expression "[MSBuild]::NormalizePath( C:\src\mesonbuild\Misc\FreeRDP-master\client\X11\xfreerdp\xfreerdp, f3f7317@@Windows resource for file 'Misc_FreeRDP-master_client_X11_xfreerdp_xfreerdp_xfreerdp.rc'@cus\, f3f7317@@Windows resource for file 'Misc_FreeRDP-master_client_X11_xfreerdp_xfreerdp_xfreerdp.rc'@cus. vcxproj.CopyComplete)" cannot be evaluated. Path: C:\src\mesonbuild\Misc\FreeRDP-master\client\X11\xfreerdp\xfreerdp\f3f7317 @@Windows resource for file 'Misc_FreeRDP-master_client_X11_xfreerdp_xfreerdp_xfreerdp.rc'@cus\f3f7317 @@Windows resource for file 'Misc_FreeRDP-master_client_X11_xfreerdp_xfreerdp_xfreerdp.rc'@cus. vcxproj.CopyComplete exceeds the OS max path limit. The fully qualified file name must be less than 260 characters.
4 years ago
res_targets.append(build.CustomTarget(name_formatted, state.subdir, state.subproject, res_kwargs))
return ModuleReturnValue(res_targets, [res_targets])
def initialize(interp: 'Interpreter') -> WindowsModule:
return WindowsModule(interp)