modules: fs: Add build_subdir argument to fs.copyfile()

Co-developed-by: Paul Caprioli <paul@hpkfft.com>
diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md
index 91c706e..abff9bc 100644
--- a/docs/markdown/Fs-module.md
+++ b/docs/markdown/Fs-module.md
@@ -186,7 +186,7 @@
 
 ### parent
 
-Returns the parent directory (i.e. dirname).
+Returns the parent directory (i.e., dirname).
 
 ```meson
 new = fs.parent('foo/bar')  # foo
@@ -195,7 +195,7 @@
 
 ### name
 
-Returns the last component of the path (i.e. basename).
+Returns the last component of the path (i.e., basename).
 
 ```meson
 fs.name('foo/bar/baz.dll.a')  # baz.dll.a
@@ -256,20 +256,23 @@
 
 *Since 0.64.0*
 
-Copy a file from the source directory to the build directory at build time
+Copy a file from the source directory to the build directory at build time.
 
 Has the following positional arguments:
    - src `File | str`: the file to copy
 
 Has the following optional arguments:
    - dest `str`: the name of the output file. If unset will be the basename of
-     the src argument
+     the src argument (i.e., the last component of its path).
 
 Has the following keyword arguments:
    - install `bool`: Whether to install the copied file, defaults to false
    - install_dir `str`: Where to install the file to
    - install_tag: `str`: the install tag to assign to this target
    - install_mode `array[str | int]`: the mode to install the file with
+   - build_subdir `str`: *since 1.12.0*.  Places the build results in a subdirectory
+     of the given name rather than directly into the build directory.
+     For more information, see [[custom_target]].
 
 returns:
    - a [[custom_target]] object
diff --git a/docs/markdown/snippets/fs-copyfile-build-subdir.md b/docs/markdown/snippets/fs-copyfile-build-subdir.md
new file mode 100644
index 0000000..f166425
--- /dev/null
+++ b/docs/markdown/snippets/fs-copyfile-build-subdir.md
@@ -0,0 +1,4 @@
+## `fs.copyfile()` now has a `build_subdir` argument
+
+`fs.copyfile()`'s new `build_subdir` argument allows creating a file
+inside a subdirectory of the current build directory.
diff --git a/docs/yaml/functions/configure_file.yaml b/docs/yaml/functions/configure_file.yaml
index 32cb559..050e863 100644
--- a/docs/yaml/functions/configure_file.yaml
+++ b/docs/yaml/functions/configure_file.yaml
@@ -20,7 +20,11 @@
 
   *(since 0.47.0)* When the `copy:` keyword argument is set to `true`,
   this function will copy the file provided in `input:` to a file in the
-  build directory with the name `output:` in the current directory.
+  current build directory with the name provided in `output:`.
+  The copy happens at configuration.  If the input file is subsequently
+  modified, Meson will regenerate the build files.
+  To perform the copy at build time (with the usual dependency on the source
+  file) use `copyfile()` from the FS (filesystem) module.
 
 warnings:
   - the `install_mode` kwarg ignored integer values between 0.62 -- 1.1.0.
diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py
index 814aa0f..ef6c4cd 100644
--- a/mesonbuild/interpreter/type_checking.py
+++ b/mesonbuild/interpreter/type_checking.py
@@ -580,6 +580,13 @@
     deprecated_message='This does not, and never has, done anything. It should be removed'
 )
 
+BUILD_SUBDIR_KW: KwargInfo[str] = KwargInfo(
+    'build_subdir',
+    str,
+    default='',
+    since='1.10.0'
+)
+
 def _objects_validator(vals: T.List[ObjectTypes]) -> T.Optional[str]:
     non_objects: T.List[str] = []
 
@@ -665,7 +672,7 @@
                 ('1.1.0', 'generated sources as positional "objects" arguments')
         },
     ),
-    KwargInfo('build_subdir', str, default='', since='1.10.0')
+    BUILD_SUBDIR_KW,
 ]
 
 
diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py
index 57a6b6d..ba74490 100644
--- a/mesonbuild/modules/fs.py
+++ b/mesonbuild/modules/fs.py
@@ -12,7 +12,8 @@
 from . import ExtensionModule, ModuleReturnValue, ModuleInfo
 from .. import mlog
 from ..build import BuildTarget, CustomTarget, CustomTargetIndex, InvalidArguments
-from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, NoneType
+from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, \
+    BUILD_SUBDIR_KW, NoneType
 from ..interpreterbase import FeatureNew, KwargInfo, typed_kwargs, typed_pos_args, noKwargs
 from ..mesonlib import File, MesonException, has_path_sep, is_windows, path_is_in_root, relpath
 
@@ -34,6 +35,7 @@
 
         """Kwargs for fs.copy"""
 
+        build_subdir: str
         install: bool
         install_dir: T.Optional[str]
         install_mode: FileMode
@@ -285,6 +287,7 @@
         INSTALL_MODE_KW,
         INSTALL_TAG_KW,
         KwargInfo('install_dir', (str, NoneType)),
+        BUILD_SUBDIR_KW.evolve(since='1.12.0'),
     )
     def copyfile(self, state: ModuleState, args: T.Tuple[FileOrString, T.Optional[str]],
                  kwargs: CopyKw) -> ModuleReturnValue:
@@ -315,6 +318,7 @@
             install_tag=[kwargs['install_tag']],
             backend=state.backend,
             description='Copying file {}',
+            build_subdir=kwargs['build_subdir'],
         )
 
         return ModuleReturnValue(ct, [ct])