Prepare release v2.4.0 (#4176)

Work towards #4175

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index abcd5ba..013d33c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,69 @@
 directory, or view the [latest generated
 changelog](https://rules-python.readthedocs.io/en/latest/changelog.html).
 
+{#v2-4-0}
+## [2.4.0] - 2026-09-22
+
+[2.4.0]: https://github.com/bazel-contrib/rules_python/releases/tag/2.4.0
+
+{#v2-4-0-changed}
+### Changed
+* (bootstrap) {obj}`sys.path` adds the Python runtime in runfiles instead
+  of the underlying Bazel repository cache directory
+  ([#4104](https://github.com/bazel-contrib/rules_python/pull/4104)).
+* (gazelle) Deprecate the `requirements` argument of `gazelle_python_manifest`
+  in favor of the format-agnostic `lockfiles` argument, which supports `uv.lock`.
+* (toolchain) Updated default Python 3.15 runtime from `3.15.0a8` to `3.15.0rc1`
+  ([#4122](https://github.com/bazel-contrib/rules_python/pull/4122)).
+* (toolchain) Updated the default Python toolchain version from `3.11` to `3.14`
+  in {obj}`python.defaults`, {obj}`python_register_toolchains`, and
+  {obj}`python_register_multi_toolchains`
+  ([#4023](https://github.com/bazel-contrib/rules_python/pull/4023)).
+
+{#v2-4-0-fixed}
+### Fixed
+* Include the Python toolchain's static libraries when `current_py_cc_libs` is used
+  as a dependency of `cc_shared_library`.
+* (bzlmod) Fixed a type mismatch error when using {obj}`coverage_tool` with
+  {obj}`python.single_version_platform_override`
+  ([#2570](https://github.com/bazel-contrib/rules_python/issues/2570)).
+* (precompile) Fixed handling of directory and `.pyc` file inputs in
+  {obj}`srcs` when {obj}`precompile` is enabled on {obj}`py_library`,
+  {obj}`py_binary`, and {obj}`py_test` targets
+  ([#4113](https://github.com/bazel-contrib/rules_python/pull/4113)).
+* (pypi) Fixed wheel RECORD file generation on Windows so that rewritten shebang
+  scripts correctly include the `.bat` extension
+  ([#4114](https://github.com/bazel-contrib/rules_python/pull/4114)).
+* (pypi) Fixed {obj}`RECORD` file paths for extracted `.data` directory contents
+  so that {obj}`importlib.metadata.files()` correctly locates installed
+  distribution files ([#4025](https://github.com/bazel-contrib/rules_python/pull/4025)).
+* (pypi) Venv mode now works on NixOS and no longer requires coreutils to process
+  wheel scripts and RECORD files
+  ([#4125](https://github.com/bazel-contrib/rules_python/pull/4125)).
+* (rules) Fixed loading Python extension modules from paths longer than `MAX_PATH` on Windows.
+* (runfiles) Updated {obj}`runfiles.Path` method signatures for Python 3.14
+  typeshed compatibility and fixed {obj}`runfiles.Path.match` on Python 3.12+.
+  ([#4023](https://github.com/bazel-contrib/rules_python/issues/4023))
+* (stamping) The build data spawn action is now skipped when `--stamp=false` and,
+  instead the metadata file is written at analysis time via `ctx.actions.write()`.
+* (toolchain) Fixed a crash in {obj}`py_test` main validation
+  ({obj}`validate_test_main`) on Windows: the interpreter used for the check
+  couldn't find its DLLs or its stdlib once relocated
+  ([#4079](https://github.com/bazel-contrib/rules_python/issues/4079)).
+* (toolchain) Fixed a crash on Windows when precompiling is enabled:
+  the precompiler's interpreter couldn't find its DLLs once relocated
+  ([#4082](https://github.com/bazel-contrib/rules_python/issues/4082)).
+
+{#v2-4-0-added}
+### Added
+* (runfiles) Added {obj}`Runfiles.CreateOrRaise` to return a `Runfiles` instance
+  or raise an error if runfiles cannot be found.
+* (uv) Added {obj}`directory` attribute to {obj}`lock` to support running `uv`
+  commands within subdirectories when generating lock files. Defaults to the
+  package directory, set it to `None` or `""` to set it to the root of the repo
+  ([#4029](https://github.com/bazel-contrib/rules_python/issues/4029)).
+
+
 {#v2-3-3}
 ## [2.3.3] - 2026-09-04
 
diff --git a/gazelle/docs/installation_and_usage.md b/gazelle/docs/installation_and_usage.md
index 16bccde..3a8733c 100644
--- a/gazelle/docs/installation_and_usage.md
+++ b/gazelle/docs/installation_and_usage.md
@@ -123,7 +123,7 @@
 )
 ```
 
-:::{versionchanged} VERSION_NEXT_FEATURE
+:::{versionchanged} 2.4.0
 The `requirements` argument is deprecated in favor of `lockfiles`, which can
 refer to any dependency lockfile, including `uv.lock`.
 :::
diff --git a/gazelle/manifest/defs.bzl b/gazelle/manifest/defs.bzl
index c61618f..a01d663 100644
--- a/gazelle/manifest/defs.bzl
+++ b/gazelle/manifest/defs.bzl
@@ -31,7 +31,7 @@
         **kwargs):
     """A macro for defining the updating and testing targets for the Gazelle manifest file.
 
-    :::{versionchanged} VERSION_NEXT_FEATURE
+    :::{versionchanged} 2.4.0
     The `requirements` argument is deprecated in favor of `lockfiles`, which
     can refer to any dependency lockfile, including `uv.lock`.
     :::
diff --git a/news/2570.fixed.md b/news/2570.fixed.md
deleted file mode 100644
index f31115d..0000000
--- a/news/2570.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(bzlmod) Fixed a type mismatch error when using {obj}`coverage_tool` with
-{obj}`python.single_version_platform_override`
-([#2570](https://github.com/bazel-contrib/rules_python/issues/2570)).
diff --git a/news/4023.changed.md b/news/4023.changed.md
deleted file mode 100644
index 56c4337..0000000
--- a/news/4023.changed.md
+++ /dev/null
@@ -1,4 +0,0 @@
-(toolchain) Updated the default Python toolchain version from `3.11` to `3.14`
-in {obj}`python.defaults`, {obj}`python_register_toolchains`, and
-{obj}`python_register_multi_toolchains`
-([#4023](https://github.com/bazel-contrib/rules_python/pull/4023)).
diff --git a/news/4025.fixed.md b/news/4025.fixed.md
deleted file mode 100644
index 80e8133..0000000
--- a/news/4025.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(pypi) Fixed {obj}`RECORD` file paths for extracted `.data` directory contents
-so that {obj}`importlib.metadata.files()` correctly locates installed
-distribution files ([#4025](https://github.com/bazel-contrib/rules_python/pull/4025)).
diff --git a/news/4029.added.md b/news/4029.added.md
deleted file mode 100644
index 07e5cec..0000000
--- a/news/4029.added.md
+++ /dev/null
@@ -1,4 +0,0 @@
-(uv) Added {obj}`directory` attribute to {obj}`lock` to support running `uv`
-commands within subdirectories when generating lock files. Defaults to the
-package directory, set it to `None` or `""` to set it to the root of the repo
-([#4029](https://github.com/bazel-contrib/rules_python/issues/4029)).
diff --git a/news/4071.fixed.md b/news/4071.fixed.md
deleted file mode 100644
index 0b24827..0000000
--- a/news/4071.fixed.md
+++ /dev/null
@@ -1 +0,0 @@
-(rules) Fixed loading Python extension modules from paths longer than `MAX_PATH` on Windows.
diff --git a/news/4079.fixed.md b/news/4079.fixed.md
deleted file mode 100644
index 7e1c323..0000000
--- a/news/4079.fixed.md
+++ /dev/null
@@ -1,4 +0,0 @@
-(toolchain) Fixed a crash in {obj}`py_test` main validation
-({obj}`validate_test_main`) on Windows: the interpreter used for the check
-couldn't find its DLLs or its stdlib once relocated
-([#4079](https://github.com/bazel-contrib/rules_python/issues/4079)).
diff --git a/news/4082.fixed.md b/news/4082.fixed.md
deleted file mode 100644
index 12c6a51..0000000
--- a/news/4082.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(toolchain) Fixed a crash on Windows when precompiling is enabled:
-the precompiler's interpreter couldn't find its DLLs once relocated
-([#4082](https://github.com/bazel-contrib/rules_python/issues/4082)).
diff --git a/news/4104.changed.md b/news/4104.changed.md
deleted file mode 100644
index e30e6ba..0000000
--- a/news/4104.changed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(bootstrap) {obj}`sys.path` adds the Python runtime in runfiles instead
-of the underlying Bazel repository cache directory
-([#4104](https://github.com/bazel-contrib/rules_python/pull/4104)).
diff --git a/news/4111.changed.md b/news/4111.changed.md
deleted file mode 100644
index bde84bc..0000000
--- a/news/4111.changed.md
+++ /dev/null
@@ -1,2 +0,0 @@
-(gazelle) Deprecate the `requirements` argument of `gazelle_python_manifest`
-in favor of the format-agnostic `lockfiles` argument, which supports `uv.lock`.
diff --git a/news/4113.fixed.md b/news/4113.fixed.md
deleted file mode 100644
index e852e1a..0000000
--- a/news/4113.fixed.md
+++ /dev/null
@@ -1,4 +0,0 @@
-(precompile) Fixed handling of directory and `.pyc` file inputs in
-{obj}`srcs` when {obj}`precompile` is enabled on {obj}`py_library`,
-{obj}`py_binary`, and {obj}`py_test` targets
-([#4113](https://github.com/bazel-contrib/rules_python/pull/4113)).
diff --git a/news/4114.fixed.md b/news/4114.fixed.md
deleted file mode 100644
index b68e75b..0000000
--- a/news/4114.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(pypi) Fixed wheel RECORD file generation on Windows so that rewritten shebang
-scripts correctly include the `.bat` extension
-([#4114](https://github.com/bazel-contrib/rules_python/pull/4114)).
diff --git a/news/4122.changed.md b/news/4122.changed.md
deleted file mode 100644
index 3c6173f..0000000
--- a/news/4122.changed.md
+++ /dev/null
@@ -1,2 +0,0 @@
-(toolchain) Updated default Python 3.15 runtime from `3.15.0a8` to `3.15.0rc1`
-([#4122](https://github.com/bazel-contrib/rules_python/pull/4122)).
diff --git a/news/4125.fixed.md b/news/4125.fixed.md
deleted file mode 100644
index 54c2096..0000000
--- a/news/4125.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(pypi) Venv mode now works on NixOS and no longer requires coreutils to process
-wheel scripts and RECORD files
-([#4125](https://github.com/bazel-contrib/rules_python/pull/4125)).
diff --git a/news/4160.fixed.md b/news/4160.fixed.md
deleted file mode 100644
index 5f87118..0000000
--- a/news/4160.fixed.md
+++ /dev/null
@@ -1,2 +0,0 @@
-(stamping) The build data spawn action is now skipped when `--stamp=false` and,
-instead the metadata file is written at analysis time via `ctx.actions.write()`.
diff --git a/news/add_runfiles_raise_api.added.md b/news/add_runfiles_raise_api.added.md
deleted file mode 100644
index 5b2ff57..0000000
--- a/news/add_runfiles_raise_api.added.md
+++ /dev/null
@@ -1,2 +0,0 @@
-(runfiles) Added {obj}`Runfiles.CreateOrRaise` to return a `Runfiles` instance
-or raise an error if runfiles cannot be found.
diff --git a/news/current-py-cc-libs-shared-library.fixed.md b/news/current-py-cc-libs-shared-library.fixed.md
deleted file mode 100644
index 9cb1229..0000000
--- a/news/current-py-cc-libs-shared-library.fixed.md
+++ /dev/null
@@ -1,2 +0,0 @@
-Include the Python toolchain's static libraries when `current_py_cc_libs` is used
-as a dependency of `cc_shared_library`.
diff --git a/news/runfiles_py314_compat.fixed.md b/news/runfiles_py314_compat.fixed.md
deleted file mode 100644
index accf48c..0000000
--- a/news/runfiles_py314_compat.fixed.md
+++ /dev/null
@@ -1,3 +0,0 @@
-(runfiles) Updated {obj}`runfiles.Path` method signatures for Python 3.14
-typeshed compatibility and fixed {obj}`runfiles.Path.match` on Python 3.12+.
-([#4023](https://github.com/bazel-contrib/rules_python/issues/4023))
diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl
index 5b842f8..e52cd8d 100644
--- a/python/private/attributes.bzl
+++ b/python/private/attributes.bzl
@@ -351,7 +351,7 @@
 allowed. Ordinary library dependencies should remain in `deps`.
 :::
 
-:::{versionchanged} VERSION_NEXT_PATCH
+:::{versionchanged} 2.4.0
 Allowed `.pyc` and directory inputs in `srcs`.
 :::
 """,
diff --git a/python/private/current_py_cc_libs.bzl b/python/private/current_py_cc_libs.bzl
index da4f8d5..e545ee7 100644
--- a/python/private/current_py_cc_libs.bzl
+++ b/python/private/current_py_cc_libs.bzl
@@ -49,7 +49,7 @@
 )
 ```
 
-:::{versionchanged} VERSION_NEXT_PATCH
+:::{versionchanged} 2.4.0
 Static libraries are included when this target is a dependency of `cc_shared_library`.
 :::
 """,
diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py
index 9650563..c15df16 100644
--- a/python/runfiles/runfiles.py
+++ b/python/runfiles/runfiles.py
@@ -835,7 +835,7 @@
         Raises:
           RuntimeError: if runfiles cannot be found.
 
-        :::{versionadded} VERSION_NEXT_FEATURE
+        :::{versionadded} 2.4.0
         :::
         """
         runfiles = Runfiles.Create(env=env)
@@ -865,7 +865,7 @@
 def CreateOrRaise(env: dict[str, str] | None = None) -> Runfiles:
     """Refer to `Runfiles.CreateOrRaise`.
 
-    :::{versionadded} VERSION_NEXT_FEATURE
+    :::{versionadded} 2.4.0
     :::
     """
     return Runfiles.CreateOrRaise(env)
diff --git a/python/uv/private/lock.bzl b/python/uv/private/lock.bzl
index 6080f13..33c301d 100644
--- a/python/uv/private/lock.bzl
+++ b/python/uv/private/lock.bzl
@@ -635,8 +635,8 @@
         constraints: {type}`list[Label]` The list of constraints files to use.
         directory: {type}`str | None` The directory into which we should cd when
             running the command. Defaults to `native.package()`. Set to `None`
-            or `""` to restore behaviour before `VERSION_NEXT_FEATURE`.
-            {versionadded} VERSION_NEXT_FEATURE
+            or `""` to restore behaviour before `2.4.0`.
+            {versionadded} 2.4.0
         generate_hashes: {type}`bool` Generate hashes for all of the
             requirements. Only meaningful for `requirements.txt` style output.
             Defaults to `True`.