Promote `rc` (Windows resource compiler) to a proper Meson language
`rc` is now a first-class Meson language, just like `nasm` or `masm`.
Users can declare it with `project('foo', 'c', 'rc')` or
`add_languages('rc')`, and pass `.rc` files directly as sources to build
targets. The `RCFLAGS` environment variable and
`add_project_arguments(language: 'rc')` now work as expected.
Fix #4736
Once we are making a real language and not hand-rolling a simulacrum of
one, it is easy to support `RCFLAGS`. In fact, it is hard to *forget* to
support `RCFLAGS` --- that's the beauty of using existing abstractions.
This is not the easiest way to fix #4736, but I do think it is the best
way long term, because it helps ensure other deviations from how
language support is supposed to work --- of which a missing `*FLAGS`
variable is just one possible example --- are also far less likely.
The issue I am most worried about is the treatment of `depends` and
`depend_files`. As described in the docs, those have to be
hand-migrated, and the result is slightly different behavior in terms of
which exact build steps depend on what. The lack of fine-grained code of
compilation vs linking build depends, or per-language depends (so we
don't have to make assumptions about compilation units) is certainly
preexisting, and arguably a problem. So one could say well that should
be fixed first before we write our shim.
Implementation details:
**Resource Compiler hierarchy** mirrors the `GnuLikeCompiler` /
`VisualStudioLikeCompiler` split used for C/C++:
- `VisualStudioLikeResourceCompiler` — `/I`, `/fo`, `/nologo`, `.res`
suffix
- `WindowsResourceCompiler` (`rc.exe`)
- `LlvmRcCompiler` (`llvm-rc`)
- `GnuLikeResourceCompiler` — `-I`, `-o`, `.o` suffix
- `WindresCompiler` (`windres`)
- `LlvmWindresCompiler` (`llvm-windres`)
- `WineResourceCompiler` (`wrc`)
**`windows.compile_resources()`** is now a thin deprecated shim that
ensures the `rc` language is detected and returns the source files for
the normal compile pipeline to handle.
Key changes:
- `mesonbuild/compilers/rc.py`: new file with the mixin hierarchy
described above
- `mesonbuild/compilers/detect.py`: `detect_rc_compiler()` function
- `mesonbuild/compilers/compilers.py`: `rc` in `Language`,
`lang_suffixes`, `CFLAGS_MAPPING`; new `get_object_suffix()` on
`Compiler` base class
- `mesonbuild/envconfig.py`: `rc` moved from `ENV_VAR_TOOL_MAP` to
`ENV_VAR_COMPILER_MAP` (supports both `RC` and `WINDRES` env vars)
- `mesonbuild/backend/backends.py`: use `compiler.get_object_suffix()`
for output file extension
- `mesonbuild/backend/ninjabackend.py`: skip link rule generation for
compilers without a linker (`rc`)
- `mesonbuild/modules/windows.py`: simplified from 196 to ~80 lines
diff --git a/docs/markdown/RC.md b/docs/markdown/RC.md
new file mode 100644
index 0000000..df613ca
--- /dev/null
+++ b/docs/markdown/RC.md
@@ -0,0 +1,72 @@
+---
+title: RC
+short-description: Compiling Windows resources
+...
+
+# Compiling Windows resources
+
+*Since 1.11.0*
+
+Meson has support for compiling Windows resource files (`.rc`). To use
+it, add `rc` to your project languages:
+
+```meson
+project('myapp', 'c', 'rc')
+
+executable('myapp', 'main.c', 'resources.rc')
+```
+
+You can also add the language conditionally, which is useful for
+cross-compilation setups where an RC compiler may not always be
+available:
+
+```meson
+project('myapp', 'c')
+
+if add_languages('rc', required: false, native: false)
+ # .rc sources can be used in targets
+endif
+```
+
+## Compiler detection
+
+The following resource compilers are detected automatically:
+
+| Compiler id | Tool | CLI style |
+|---------------|-------------|------------|
+| rc | Microsoft `rc.exe` | MSVC |
+| llvm-rc | LLVM `llvm-rc` | MSVC |
+| windres | GNU `windres` | GCC |
+| llvm-windres | LLVM `llvm-windres`| GCC |
+| wrc | Wine `wrc` | GCC |
+
+Meson will look for the `rc` binary in the `[binaries]` section of
+your machine file, or through the `RC` and `WINDRES` environment
+variables.
+
+## Passing arguments
+
+Extra flags can be passed to the resource compiler using the standard
+Meson mechanisms:
+
+```meson
+# Via project arguments
+add_project_arguments('-DPROJECT_DEF', language: 'rc')
+
+# Via per-target keyword argument
+executable('myapp', 'main.c', 'resources.rc',
+ rc_args: ['-DLIB_BUILD'])
+```
+
+The `RCFLAGS` environment variable is also respected.
+
+## Include directories
+
+Include directories work the same as for other languages:
+
+```meson
+inc = include_directories('res')
+executable('myapp', 'main.c', 'resources.rc',
+ include_directories: inc)
+```
+
diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md
index faa039b..6bedf75 100644
--- a/docs/markdown/Reference-tables.md
+++ b/docs/markdown/Reference-tables.md
@@ -35,6 +35,11 @@
| open64 | The Open64 Fortran Compiler | |
| pathscale | The Pathscale Fortran compiler | |
| pgi | Portland PGI C/C++/Fortran compilers | |
+| rc | Microsoft rc.exe (Since 1.11.0) | msvc |
+| llvm-rc | LLVM llvm-rc (Since 1.11.0) | msvc |
+| windres | GNU windres (Since 1.11.0) | gcc |
+| llvm-windres | LLVM llvm-windres (Since 1.11.0) | gcc |
+| wrc | Wine Resource Compiler (Since 1.11.0) | |
| rustc | Rust compiler | |
| sun | Sun Fortran compiler | |
| c2000 | Texas Instruments C/C++ Compiler (C2000) | |
@@ -239,6 +244,7 @@
| Cython | cython_args | cython_link_args |
| NASM | nasm_args | N/A |
| MASM | masm_args | N/A |
+| RC | rc_args | N/A |
| Linear ASM | linearasm_args | N/A |
All these `<lang>_*` options are specified per machine. See in
@@ -268,6 +274,7 @@
| VALAFLAGS | Flags for the Vala compiler |
| RUSTFLAGS | Flags for the Rust compiler |
| CYTHONFLAGS | Flags for the Cython compiler |
+| RCFLAGS | Flags for the RC (resource) compiler |
| LDFLAGS | The linker flags, used for all languages |
N.B. these settings are specified per machine, and so the environment
@@ -399,6 +406,7 @@
| Vala | VALAC | | Use CC_LD. Vala transpiles to C |
| C# | CSC | CSC | The linker is the compiler |
| Cython | CYTHON | | |
+| RC | RC, WINDRES | | Since 1.11.0 |
| nasm | NASM | | Uses the C linker |
| archiver | | AR | |
diff --git a/docs/markdown/Windows-module.md b/docs/markdown/Windows-module.md
index 6e4888e..a933513 100644
--- a/docs/markdown/Windows-module.md
+++ b/docs/markdown/Windows-module.md
@@ -7,22 +7,25 @@
### compile_resources
+*Deprecated since 1.11.0*: Use the [`rc` language](RC.md) instead.
+The function continues to work as a thin compatibility shim, but new
+projects should use the `rc` language directly.
+
```
windows = import('windows')
windows.compile_resources(...(string | File | CustomTarget | CustomTargetIndex),
args: []string,
depend_files: [](string | File),
depends: [](BuildTarget | CustomTarget | CustomTargetIndex)
- include_directories: [](IncludeDirectories | string)): []CustomTarget
+ include_directories: [](IncludeDirectories | string)): [](File | CustomTarget | CustomTargetIndex)
implicit_include_directories: bool
```
Compiles Windows `rc` files specified in the positional arguments.
-Returns a list of `CustomTarget` objects that you put in the list of sources for
-the target you want to have the resources in.
+Returns the sources for inclusion in a build target.
-*Since 0.61.0* CustomTargetIndexes and CustomTargets with more than out output
-*may be used as positional arguments.
+*Since 0.61.0* CustomTargetIndexes and CustomTargets with more than one output
+may be used as positional arguments.
This method has the following keyword arguments:
@@ -38,10 +41,37 @@
- `implicit_include_directories` Controls whether Meson adds
the current source and build directories to the include path (*since 1.11.0*)
-The resource compiler executable used is the first which exists from the
-following list:
+#### Migrating to the `rc` language
-1. The `windres` executable given in the `[binaries]` section of the cross-file
-2. The `RC` environment variable
-3. The `WINDRES` environment variable
-4. The resource compiler which is part of the same toolset as the C or C++ compiler in use.
+Replace:
+
+```meson
+windows = import('windows')
+resources = windows.compile_resources('resource.rc',
+ args: ['-DSOME_DEF'],
+ depend_files: ['icon.ico', 'manifest.xml'],
+ depends: [my_icon_generator],
+ include_directories: include_directories('inc'))
+executable('myapp', 'main.c', resources)
+```
+
+with:
+
+```meson
+project('myapp', 'c', 'rc')
+add_project_arguments('-DSOME_DEF', language: 'rc')
+executable('myapp', 'main.c', 'resource.rc',
+ depend_files: ['icon.ico', 'manifest.xml'],
+ depends: [my_icon_generator],
+ include_directories: include_directories('inc'))
+```
+
+The `depend_files` and `depends` keyword arguments can be passed to
+the build target instead. However, note that on a build target these
+only affect link-step ordering, not individual compile steps. In
+practice this is rarely a problem because most `rc` compilers (GNU
+`windres`, LLVM `llvm-windres`, and Microsoft `rc.exe` via Meson's
+internal wrapper) generate depfiles that let ninja discover included
+files automatically at compile time. For `depends`, if the dependency
+is a generated `.rc` source, pass it directly as a source to the build
+target and the ordering will be handled naturally.
diff --git a/docs/markdown/snippets/rc-language.md b/docs/markdown/snippets/rc-language.md
new file mode 100644
index 0000000..3f40aa1
--- /dev/null
+++ b/docs/markdown/snippets/rc-language.md
@@ -0,0 +1,13 @@
+## `rc` is now a first-class language
+
+Windows resource compilation (`.rc` files) is now supported as a proper
+Meson language. See [RC](RC.md) for full documentation.
+
+## `windows.compile_resources()` is deprecated
+
+`windows.compile_resources()` is deprecated in favor of the new `rc`
+language. Calls to it will emit a deprecation warning. The function
+continues to work as a thin compatibility shim that delegates to the
+`rc` language pipeline internally, so existing projects will not break.
+See [Windows module](Windows-module.md#compile_resources) for migration
+guidance.
diff --git a/docs/sitemap.txt b/docs/sitemap.txt
index a71e954..7ce932c 100644
--- a/docs/sitemap.txt
+++ b/docs/sitemap.txt
@@ -62,6 +62,7 @@
i18n-module.md
Wayland-module.md
Java.md
+ RC.md
Vala.md
D.md
Cython.md
diff --git a/docs/yaml/functions/project.yaml b/docs/yaml/functions/project.yaml
index 4e9a6e3..4730989 100644
--- a/docs/yaml/functions/project.yaml
+++ b/docs/yaml/functions/project.yaml
@@ -24,7 +24,7 @@
Supported values for languages are `c`, `cpp` (for `C++`), `cuda`,
`cython`, `d`, `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`),
- `swift`, `nasm`, `masm`, `linearasm`, `vala` and `rust`.
+ `swift`, `nasm`, `masm`, `linearasm`, `rc`, `vala` and `rust`.
posargs:
project_name:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 7494b1a..dcafee0 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -798,7 +798,8 @@
gen_source = os.path.relpath(os.path.join(build_dir, rel_src),
os.path.join(self.environment.get_source_dir(), target.get_subdir()))
machine = self.environment.machines[target.for_machine]
- object_suffix = machine.get_object_suffix()
+ compiler_suffix = compiler.get_object_suffix()
+ object_suffix = compiler_suffix if compiler_suffix is not None else machine.get_object_suffix()
# For the TASKING compiler, in case of LTO or prelinking the object suffix has to be .mil
if compiler.get_id() == 'tasking':
use_lto = self.get_target_option(target, 'b_lto')
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index b45dbaa..0786a36 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -2492,6 +2492,9 @@
for langname, compiler in complist.items():
if langname in {'java', 'vala', 'rust', 'cs', 'cython'}:
continue
+ # RC compilers produce object files but don't drive linking
+ if compiler.linker is None and langname == 'rc':
+ continue
rule = '{}_LINKER{}'.format(langname, self.get_rule_suffix(for_machine))
command = compiler.get_linker_exelist()
args = ['$ARGS'] + NinjaCommandArg.list(compiler.get_linker_output_args('$out'), Quoting.none) + ['$in', '$LINK_ARGS']
diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py
index f645090..305ecc8 100644
--- a/mesonbuild/compilers/__init__.py
+++ b/mesonbuild/compilers/__init__.py
@@ -42,6 +42,7 @@
'detect_rust_compiler',
'detect_d_compiler',
'detect_swift_compiler',
+ 'detect_rc_compiler',
]
# Bring symbols from each module into compilers sub-package namespace
@@ -85,4 +86,5 @@
detect_rust_compiler,
detect_d_compiler,
detect_swift_compiler,
+ detect_rc_compiler,
)
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 370ccc3..0c59ba0 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -40,7 +40,7 @@
# See the comment on `lang_suffixes` if modifying this list.
Language = Literal[
'c', 'cpp', 'cuda', 'fortran', 'd', 'objc', 'objcpp', 'rust', 'vala',
- 'cs', 'swift', 'java', 'cython', 'nasm', 'masm', 'linearasm'
+ 'cs', 'swift', 'java', 'cython', 'nasm', 'masm', 'linearasm', 'rc'
]
CompilerDict: TypeAlias = T.Dict[Language, 'Compiler']
@@ -79,6 +79,7 @@
'nasm': ('asm', 'nasm',),
'masm': ('masm',),
'linearasm': ('sa',),
+ 'rc': ('rc',),
}
# Some compilers only recognize files with specific suffixes.
compiler_suffixes: T.Mapping[str, T.Tuple[str, ...]] = {
@@ -123,6 +124,7 @@
'rust': 'RUSTFLAGS',
'cython': 'CYTHONFLAGS',
'cs': 'CSFLAGS', # This one might not be standard.
+ 'rc': 'RCFLAGS',
}
# All these are only for C-linkable languages; see `clink_langs` above.
@@ -569,6 +571,13 @@
def get_default_suffix(self) -> str:
return self.default_suffix
+ def get_object_suffix(self) -> T.Optional[str]:
+ """Override the default object suffix for this compiler.
+
+ Returns None to use the machine default (.o or .obj).
+ """
+ return None
+
def get_define(self, dname: str, prefix: str,
extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]],
dependencies: T.List['Dependency'],
diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py
index bc2bc90..bf5786a 100644
--- a/mesonbuild/compilers/detect.py
+++ b/mesonbuild/compilers/detect.py
@@ -82,6 +82,7 @@
defaults['clang_static_linker'] = ['llvm-ar']
defaults['emxomf_static_linker'] = ['emxomfar']
defaults['nasm'] = ['nasm', 'yasm']
+defaults['rc'] = ['rc', 'windres', 'llvm-rc', 'llvm-windres']
def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]:
@@ -102,6 +103,7 @@
'nasm': detect_nasm_compiler,
'masm': detect_masm_compiler,
'linearasm': detect_linearasm_compiler,
+ 'rc': detect_rc_compiler,
}
return lang_map[lang](env, for_machine) if lang in lang_map else None
@@ -1445,6 +1447,68 @@
_handle_exceptions(popen_exceptions, [comp])
raise EnvironmentException('Unreachable code (exception to make mypy happy)')
+def detect_rc_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler':
+ from .rc import (WindowsResourceCompiler, LlvmRcCompiler,
+ WindresCompiler, LlvmWindresCompiler, WineResourceCompiler)
+
+ # Try to find the RC compiler from the cross/native file or environment.
+ # Support both 'rc' and legacy 'windres' keys in the binaries section.
+ value = env.lookup_binary_entry(for_machine, 'rc')
+ if value is None:
+ value = env.lookup_binary_entry(for_machine, 'windres')
+
+ if value is not None:
+ compilers = [value]
+ else:
+ if not env.machines.matches_build_machine(for_machine):
+ raise EnvironmentException("'rc' compiler binary not defined in cross file [binaries] section")
+ # Determine defaults based on the C/C++ compiler
+ c_compilers = env.coredata.compilers[for_machine]
+ comp = None
+ for l in ('c', 'cpp'):
+ if l in c_compilers:
+ comp = c_compilers[l]
+ break
+ if comp is not None and (comp.id in {'msvc', 'clang-cl', 'intel-cl'} or
+ (comp.linker and comp.linker.id in {'link', 'lld-link'})):
+ compilers = [['rc'], ['llvm-rc']]
+ else:
+ compilers = [['windres'], ['llvm-windres']]
+
+ popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {}
+ for comp in compilers:
+ for (arg, match, rc_class) in [
+ ('/?', r'^.*Microsoft.*Resource Compiler.*$', WindowsResourceCompiler),
+ ('/?', r'LLVM Resource Converter.*$', LlvmRcCompiler),
+ ('--version', r'^.*GNU windres.*$', WindresCompiler),
+ ('--version', r'^.*llvm-windres.*$', LlvmWindresCompiler),
+ ('--version', r'^.*Wine Resource Compiler.*$', WineResourceCompiler),
+ ]:
+ try:
+ p, o, e = Popen_safe(comp + [arg])
+ except OSError as e_exc:
+ popen_exceptions[' '.join(comp + [arg])] = e_exc
+ continue
+ m = re.search(match, o, re.MULTILINE)
+ if m:
+ version = search_version(o)
+ # For MSVC rc.exe, find cl.exe for dependency tracking
+ if rc_class is WindowsResourceCompiler:
+ cl_path = None
+ c_compilers = env.coredata.compilers[for_machine]
+ for l in ('c', 'cpp'):
+ if l in c_compilers:
+ cl_path = c_compilers[l].get_exelist(False)[0]
+ break
+ env.add_lang_args(rc_class.language, rc_class, for_machine)
+ return rc_class(comp, version, for_machine, env, cl_path=cl_path)
+ else:
+ env.add_lang_args(rc_class.language, rc_class, for_machine)
+ return rc_class(comp, version, for_machine, env)
+
+ _handle_exceptions(popen_exceptions, compilers)
+ raise EnvironmentException('Unreachable code (exception to make mypy happy)')
+
# GNU/Clang defines and version
# =============================
diff --git a/mesonbuild/compilers/rc.py b/mesonbuild/compilers/rc.py
new file mode 100644
index 0000000..a068b29
--- /dev/null
+++ b/mesonbuild/compilers/rc.py
@@ -0,0 +1,187 @@
+# SPDX-License-Identifier: Apache-2.0
+
+from __future__ import annotations
+
+import os
+import typing as T
+
+from .compilers import Compiler
+from ..mesonlib import get_meson_command
+
+if T.TYPE_CHECKING:
+ from ..environment import Environment
+ from ..linkers.linkers import DynamicLinker
+ from ..mesonlib import MachineChoice
+
+
+class RCCompiler(Compiler):
+
+ """Base class for Windows Resource Compilers."""
+
+ language = 'rc'
+
+ def __init__(self, exelist: T.List[str], version: str,
+ for_machine: 'MachineChoice', env: 'Environment',
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ super().__init__([], exelist, version, for_machine, env, linker, full_version)
+
+ def needs_static_linker(self) -> bool:
+ return False
+
+ def can_linker_accept_rsp(self) -> bool:
+ return False
+
+ def sanity_check(self, work_dir: str) -> None:
+ return None
+
+ def _sanity_check_source_code(self) -> str:
+ return ''
+
+ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
+ return []
+
+ def get_debug_args(self, is_debug: bool) -> T.List[str]:
+ return []
+
+ def get_pic_args(self) -> T.List[str]:
+ return []
+
+ def get_werror_args(self) -> T.List[str]:
+ return []
+
+ def get_crt_compile_args(self, crt_val: str, env: 'Environment') -> T.List[str]:
+ return []
+
+ def get_compile_only_args(self) -> T.List[str]:
+ return []
+
+
+class VisualStudioLikeResourceCompiler(RCCompiler):
+
+ """Base for resource compilers with MSVC-style CLI (/I, /fo, /nologo)."""
+
+ def get_object_suffix(self) -> T.Optional[str]:
+ return 'res'
+
+ def get_always_args(self) -> T.List[str]:
+ return ['/nologo']
+
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['/fo' + outputname]
+
+ def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
+ if not path:
+ path = '.'
+ return ['/I', path]
+
+ def get_depfile_format(self) -> str:
+ return 'msvc'
+
+ def depfile_for_object(self, objfile: str) -> T.Optional[str]:
+ # Dependency tracking is handled by the --internal rc wrapper
+ # using cl.exe /showIncludes, which uses msvc depfile format.
+ # The wrapper outputs deps to stdout, not to a separate file.
+ return None
+
+ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
+ build_dir: str) -> T.List[str]:
+ for idx, i in enumerate(parameter_list):
+ if i[:2] == '/I' and len(i) > 2:
+ parameter_list[idx] = '/I' + os.path.normpath(os.path.join(build_dir, i[2:]))
+ elif i == '/I' and idx + 1 < len(parameter_list):
+ parameter_list[idx + 1] = os.path.normpath(os.path.join(build_dir, parameter_list[idx + 1]))
+ return parameter_list
+
+
+class GnuLikeResourceCompiler(RCCompiler):
+
+ """Base for resource compilers with GNU-style CLI (-I, -o)."""
+
+ def get_always_args(self) -> T.List[str]:
+ return []
+
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['-o', outputname]
+
+ def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
+ if not path:
+ path = '.'
+ return ['-I' + path]
+
+ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
+ build_dir: str) -> T.List[str]:
+ for idx, i in enumerate(parameter_list):
+ if i[:2] == '-I':
+ parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
+ return parameter_list
+
+
+class WindowsResourceCompiler(VisualStudioLikeResourceCompiler):
+
+ """Microsoft rc.exe."""
+
+ id = 'rc'
+
+ def __init__(self, exelist: T.List[str], version: str,
+ for_machine: 'MachineChoice', env: 'Environment',
+ cl_path: T.Optional[str] = None,
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ super().__init__(exelist, version, for_machine, env, linker, full_version)
+ self.cl_path = cl_path
+
+ def get_exelist(self, ccache: bool = True) -> T.List[str]:
+ exelist = super().get_exelist(ccache)
+ if self.cl_path:
+ return get_meson_command() + ['--internal', 'rc',
+ '--cl', self.cl_path,
+ '--rc'] + exelist
+ return exelist
+
+
+class LlvmRcCompiler(VisualStudioLikeResourceCompiler):
+
+ """LLVM llvm-rc."""
+
+ id = 'llvm-rc'
+
+
+class WindresCompiler(GnuLikeResourceCompiler):
+
+ """GNU windres."""
+
+ id = 'windres'
+
+ def get_depfile_suffix(self) -> str:
+ return 'd'
+
+ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
+ return ['--preprocessor-arg=-MD',
+ '--preprocessor-arg=-MQ' + outtarget,
+ '--preprocessor-arg=-MF' + outfile]
+
+
+class LlvmWindresCompiler(GnuLikeResourceCompiler):
+
+ """LLVM llvm-windres."""
+
+ id = 'llvm-windres'
+
+ def get_depfile_suffix(self) -> str:
+ return 'd'
+
+ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
+ return ['--preprocessor-arg=-MD',
+ '--preprocessor-arg=-MQ' + outtarget,
+ '--preprocessor-arg=-MF' + outfile]
+
+
+class WineResourceCompiler(GnuLikeResourceCompiler):
+
+ """Wine Resource Compiler."""
+
+ id = 'wrc'
+
+ def depfile_for_object(self, objfile: str) -> T.Optional[str]:
+ return None
diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py
index 7e19505..66913ab 100644
--- a/mesonbuild/envconfig.py
+++ b/mesonbuild/envconfig.py
@@ -108,6 +108,7 @@
'rust': ['RUSTC'],
'vala': ['VALAC'],
'nasm': ['NASM'],
+ 'rc': ['RC', 'WINDRES'],
# Linkers
'c_ld': ['CC_LD'],
@@ -133,7 +134,6 @@
'size': ['SIZE'],
'strings': ['STRINGS'],
'strip': ['STRIP'],
- 'windres': ['RC', 'WINDRES'],
# Other tools
'cmake': ['CMAKE'],
diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py
index 727eb73..399882c 100644
--- a/mesonbuild/modules/windows.py
+++ b/mesonbuild/modules/windows.py
@@ -3,9 +3,6 @@
from __future__ import annotations
-import enum
-import os
-import re
import typing as T
@@ -14,17 +11,14 @@
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
+from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args
+from ..mesonlib import MachineChoice
if T.TYPE_CHECKING:
from . import ModuleState
- from ..compilers.compilers import Language, Compiler
from ..interpreter import Interpreter
- from ..programs import CommandList
- from typing_extensions import Literal, TypedDict
+ from typing_extensions import TypedDict
class CompileResources(TypedDict):
@@ -35,77 +29,16 @@
args: T.List[str]
-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[Language, 'Compiler']) -> 'Compiler':
- # https://github.com/python/mypy/issues/18826
- # However, we need to support versions of mypy that cannot deduce the
- # tuple either.
- for l in T.cast('T.Tuple[Language, ...]', ('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():
- def search_programs(names: T.List[str]) -> T.Optional[ExternalProgram]:
- for name in names:
- program = ExternalProgram(name, silent=True)
- if program.found():
- return program
- return None
-
- 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'}):
- rescomp = search_programs(['rc', 'llvm-rc'])
- else:
- rescomp = search_programs(['windres', 'llvm-windres'])
-
- if not rescomp:
- 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',
@@ -118,107 +51,44 @@
def compile_resources(self, state: 'ModuleState',
args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]],
kwargs: 'CompileResources') -> ModuleReturnValue:
+ mlog.deprecation('windows.compile_resources() is deprecated, '
+ 'Meson now has support for the \'rc\' language. '
+ 'pass .rc files directly to build targets instead.',
+ location=state.current_node)
+
+ if kwargs['depend_files']:
+ mlog.warning('windows.compile_resources() depend_files is ignored, '
+ 'pass depend_files to the build target instead.',
+ location=state.current_node)
+ if kwargs['depends']:
+ mlog.warning('windows.compile_resources() depends is ignored, '
+ 'pass depends to the build target instead.',
+ location=state.current_node)
+
+ # Ensure the 'rc' language is detected so the normal compile pipeline
+ # can handle .rc files.
+ state.add_language('rc', MachineChoice.HOST)
+
+ # Inject args and include_directories into project args for the rc
+ # language, so the normal compile pipeline picks them up.
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, [self.interpreter.backend.get_target_dir(d)])
- ])
- extra_args += state.get_include_args(kwargs['include_directories'], kwargs['implicit_include_directories'])
+ extra_args += state.get_include_args(
+ kwargs['include_directories'], kwargs['implicit_include_directories'])
+ if extra_args:
+ rc_args = state.project_args.get('rc', [])
+ state.project_args['rc'] = rc_args + extra_args
- 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@']
+ # Convert string sources to File objects; pass through
+ # CustomTarget/CustomTargetIndex as-is (they are generated .rc files).
+ sources: T.List[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex]] = []
+ for src in args[0]:
+ if isinstance(src, str):
+ sources.append(mesonlib.File.from_source_file(
+ state.environment.source_dir, state.subdir, src))
+ else:
+ sources.append(src)
- 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}'
- depfile: T.Optional[str] = None
- depfile_type: T.Optional[Literal['gcc', 'msvc']] = None
- command: CommandList = []
-
- if rescomp_type == ResourceCompilerType.rc:
- compiler = self.detect_compiler(state.environment.coredata.compilers[MachineChoice.HOST])
- depfile_type = 'msvc'
-
- command.extend(state.environment.get_build_command())
- command.extend(['--internal', 'rc',
- '--cl', compiler.get_exelist(False)[0],
- '--rc'])
-
- command.append(rescomp)
- command.extend(res_args)
-
- # instruct binutils windres to generate a preprocessor depfile
- if rescomp_type == ResourceCompilerType.windres:
- depfile = f'{output}.d'
- depfile_type = 'gcc'
- 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,
- depfile_type=depfile_type,
- depend_files=wrc_depend_files,
- extra_depends=wrc_depends,
- description='Compiling Windows resource {}',
- ))
-
- return ModuleReturnValue(res_targets, [res_targets])
+ return ModuleReturnValue(sources, [sources])
def initialize(interp: 'Interpreter') -> WindowsModule:
return WindowsModule(interp)
diff --git a/test cases/common/295 rc language/meson.build b/test cases/common/295 rc language/meson.build
new file mode 100644
index 0000000..f566d28
--- /dev/null
+++ b/test cases/common/295 rc language/meson.build
@@ -0,0 +1,15 @@
+project('rc language', 'c')
+
+if not add_languages('rc', required: false, native: false)
+ error('MESON_SKIP_TEST: rc compiler not found')
+endif
+
+# Test add_project_arguments with rc language
+add_project_arguments('-DPROJECT_DEF', language: 'rc', native: false)
+
+# Test that .rc files work directly as sources in static_library
+lib = static_library('rclib', 'resource.rc')
+
+# Test rc_args on a target
+lib2 = static_library('rclib2', 'resource.rc',
+ rc_args : ['-DLIB_BUILD'])
diff --git a/test cases/common/295 rc language/resource.rc b/test cases/common/295 rc language/resource.rc
new file mode 100644
index 0000000..1997b8e
--- /dev/null
+++ b/test cases/common/295 rc language/resource.rc
@@ -0,0 +1 @@
+a RCDATA { "a" }