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" }