windows.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. # Copyright 2015 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. import enum
  12. import os
  13. import re
  14. from .. import mlog
  15. from .. import mesonlib, build
  16. from ..mesonlib import MachineChoice, MesonException, extract_as_list, unholder
  17. from . import get_include_args
  18. from . import ModuleReturnValue
  19. from . import ExtensionModule
  20. from ..interpreter import CustomTargetHolder
  21. from ..interpreterbase import permittedKwargs, FeatureNewKwargs, flatten
  22. from ..dependencies import ExternalProgram
  23. class ResourceCompilerType(enum.Enum):
  24. windres = 1
  25. rc = 2
  26. class WindowsModule(ExtensionModule):
  27. def detect_compiler(self, compilers):
  28. for l in ('c', 'cpp'):
  29. if l in compilers:
  30. return compilers[l]
  31. raise MesonException('Resource compilation requires a C or C++ compiler.')
  32. def _find_resource_compiler(self, state):
  33. # FIXME: Does not handle `native: true` executables, see
  34. # See https://github.com/mesonbuild/meson/issues/1531
  35. # Take a parameter instead of the hardcoded definition below
  36. for_machine = MachineChoice.HOST
  37. if hasattr(self, '_rescomp'):
  38. return self._rescomp
  39. # Will try cross / native file and then env var
  40. rescomp = ExternalProgram.from_bin_list(state.environment, for_machine, 'windres')
  41. if not rescomp or not rescomp.found():
  42. comp = self.detect_compiler(state.environment.coredata.compilers[for_machine])
  43. if comp.id in {'msvc', 'clang-cl', 'intel-cl'}:
  44. rescomp = ExternalProgram('rc', silent=True)
  45. else:
  46. rescomp = ExternalProgram('windres', silent=True)
  47. if not rescomp.found():
  48. raise MesonException('Could not find Windows resource compiler')
  49. for (arg, match, rc_type) in [
  50. ('/?', '^.*Microsoft.*Resource Compiler.*$', ResourceCompilerType.rc),
  51. ('--version', '^.*GNU windres.*$', ResourceCompilerType.windres),
  52. ]:
  53. p, o, e = mesonlib.Popen_safe(rescomp.get_command() + [arg])
  54. m = re.search(match, o, re.MULTILINE)
  55. if m:
  56. mlog.log('Windows resource compiler: %s' % m.group())
  57. self._rescomp = (rescomp, rc_type)
  58. break
  59. else:
  60. raise MesonException('Could not determine type of Windows resource compiler')
  61. return self._rescomp
  62. @FeatureNewKwargs('windows.compile_resources', '0.47.0', ['depend_files', 'depends'])
  63. @permittedKwargs({'args', 'include_directories', 'depend_files', 'depends'})
  64. def compile_resources(self, state, args, kwargs):
  65. extra_args = mesonlib.stringlistify(flatten(kwargs.get('args', [])))
  66. wrc_depend_files = extract_as_list(kwargs, 'depend_files', pop = True)
  67. wrc_depends = extract_as_list(kwargs, 'depends', pop = True)
  68. for d in wrc_depends:
  69. if isinstance(d, CustomTargetHolder):
  70. extra_args += get_include_args([d.outdir_include()])
  71. inc_dirs = extract_as_list(kwargs, 'include_directories', pop = True)
  72. for incd in inc_dirs:
  73. if not isinstance(incd.held_object, (str, build.IncludeDirs)):
  74. raise MesonException('Resource include dirs should be include_directories().')
  75. extra_args += get_include_args(inc_dirs)
  76. rescomp, rescomp_type = self._find_resource_compiler(state)
  77. if rescomp_type == ResourceCompilerType.rc:
  78. # RC is used to generate .res files, a special binary resource
  79. # format, which can be passed directly to LINK (apparently LINK uses
  80. # CVTRES internally to convert this to a COFF object)
  81. suffix = 'res'
  82. res_args = extra_args + ['/nologo', '/fo@OUTPUT@', '@INPUT@']
  83. else:
  84. # ld only supports object files, so windres is used to generate a
  85. # COFF object
  86. suffix = 'o'
  87. res_args = extra_args + ['@INPUT@', '@OUTPUT@']
  88. m = 'Argument {!r} has a space which may not work with windres due to ' \
  89. 'a MinGW bug: https://sourceware.org/bugzilla/show_bug.cgi?id=4933'
  90. for arg in extra_args:
  91. if ' ' in arg:
  92. mlog.warning(m.format(arg))
  93. res_targets = []
  94. def add_target(src):
  95. if isinstance(src, list):
  96. for subsrc in src:
  97. add_target(subsrc)
  98. return
  99. src = unholder(src)
  100. if isinstance(src, str):
  101. name_format = 'file {!r}'
  102. name = os.path.join(state.subdir, src)
  103. elif isinstance(src, mesonlib.File):
  104. name_format = 'file {!r}'
  105. name = src.relative_name()
  106. elif isinstance(src, build.CustomTarget):
  107. if len(src.get_outputs()) > 1:
  108. raise MesonException('windows.compile_resources does not accept custom targets with more than 1 output.')
  109. name_format = 'target {!r}'
  110. name = src.get_id()
  111. else:
  112. raise MesonException('Unexpected source type {!r}. windows.compile_resources accepts only strings, files, custom targets, and lists thereof.'.format(src))
  113. # Path separators are not allowed in target names
  114. name = name.replace('/', '_').replace('\\', '_')
  115. res_kwargs = {
  116. 'output': name + '_@BASENAME@.' + suffix,
  117. 'input': [src],
  118. 'command': [rescomp] + res_args,
  119. 'depend_files': wrc_depend_files,
  120. 'depends': wrc_depends,
  121. }
  122. # instruct binutils windres to generate a preprocessor depfile
  123. if rescomp_type == ResourceCompilerType.windres:
  124. res_kwargs['depfile'] = res_kwargs['output'] + '.d'
  125. res_kwargs['command'] += ['--preprocessor-arg=-MD', '--preprocessor-arg=-MQ@OUTPUT@', '--preprocessor-arg=-MF@DEPFILE@']
  126. res_targets.append(build.CustomTarget('Windows resource for ' + name_format.format(name), state.subdir, state.subproject, res_kwargs))
  127. add_target(args)
  128. return ModuleReturnValue(res_targets, [res_targets])
  129. def initialize(*args, **kwargs):
  130. return WindowsModule(*args, **kwargs)