Repository: python-wheel-build/fromager

Issue:
Update docs to explain the use of changelogs
Probably the last step once everything is implemented. 
Make sure to include:
- clarification that the order and content of the changelog doesn't matter to fromager and only the number of changelogs is useful to the tool
- user is responsible for the manual update of the changelog when plugins/env files/settings changes
- extra metadata info that is available in the wheels
- plugin to add extra metadata

Discussion:

Patch:
diff --git a/docs/customization.md b/docs/customization.md
index 735f19c..8396914 100644
--- a/docs/customization.md
+++ b/docs/customization.md
@@ -378,7 +378,22 @@ The return value is ignored.
 ```python
 def build_wheel(ctx, build_env, extra_environ, req, sdist_root_dir):
     ...
-```
+```  
+
+### add_extra_metadata_to_wheels  
+
+The `add_extra_metadata_to_wheels()` function is responsible to return any
+data the user would like to include in the wheels that fromager builds. This
+data will be added to the `fromager-build-settings` file under the `.dist-info`
+directory of the wheels. This file already contains the settings used to build
+that package.  
+
+The arguments available are `WorkContext`, `Requirement` being evaluated, the resolved
+`Version` of that requirement, a `dict` with extra environment variables, a `Path` to
+the root directory of the source distribution and a `Path` to the `.dist-info` directory
+of the wheel.  
+
+The return value must be a `dict`, otherwise it will be ignored.
 
 ## Canonical distribution names
 
@@ -477,3 +492,27 @@ resolver_dist:
     include_sdists: false
 build_dir: directory name relative to sdist directory, defaults to an empty string, which means to use the sdist directory
 ```
+
+## Speeding up build-sequence  
+
+The `build-sequence` command can be sped up by passing the `--skip-existing` option and the URL of the wheels index which hosts all the previosuly built wheels. When using `build-sequence` in skip existing mode, it will check the wheels index to see if the version the wheel it is trying to build already exists. If it does then it won't build that package.  
+
+However, there might be cases where we need to build a package again even if the version hasn't changed. This might be due to many factors such as change in plugin code, environment variables, additional patches to sdist etc. For such situations, whenever such a change occurs the user can define a changelog in the settings file for that particular version of the package:  
+
+```yaml
+# settings/torch.yaml
+changelog:
+    "2.3.1":
+        - Changed env variable MAX_JOBS to 10
+```
+
+Changelogs help fromager control the [build tag](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-format) of a wheel. Even if the version matches but the build tag doesn't the package will be built with the new build tag even in skip existing mode. By default all packages are built with build tag 0 when no changelog is specified. Build tags are added to any wheel built by fromager using any command i.e. wheels built by `bootstrap` and `step` commands also have build tags in them. The order and content of the changelog itself is irrelevant to fromager and is only meant to help user keep track why a new build tag was needed. For fromager only the number of entries in the changelog is important. The user is responsible to maintain the correct number of entries in the changelog and any decrease in the number of entries might result in an error when using `build-sequence` in the skip existing mode.  
+
+Sometimes, it might be necessary to rebuild all the packages even in skip existing mode. This might be due to complete changes in the build environment for all wheels or a breaking change in a core build dependency such as `setuptools`. Instead of defining a changelog for all packages, the user can define a global changelog which will cause a bump to the build tag for all packages:  
+
+```yaml
+# global settings.yaml
+changelog:
+    variant:
+        - Changed env variable MAX_JOBS to 10
+```
diff --git a/src/fromager/commands/build.py b/src/fromager/commands/build.py
index f435690..17ef2a6 100644
--- a/src/fromager/commands/build.py
+++ b/src/fromager/commands/build.py
@@ -173,9 +173,7 @@ def _is_wheel_built(
     try:
         logger.info(f"{req.name}: checking if {req} was already built")
         url, _ = sdist.resolve_prebuilt_wheel(wkctx, req, [wkctx.wheel_server_url])
-        build_tag_from_settings = wkctx.settings.build_tag(
-            req.name, dist_version, wkctx.variant
-        )
+        build_tag_from_settings = wkctx.settings.build_tag(req.name, dist_version)
         build_tag = build_tag_from_settings if build_tag_from_settings else (0, "")
         wheel_filename = urlparse(url).path.rsplit("/", 1)[-1]
         _, _, build_tag_from_name, _ = parse_wheel_filename(wheel_filename)
diff --git a/src/fromager/sdist.py b/src/fromager/sdist.py
index f7522ef..1e293be 100644
--- a/src/fromager/sdist.py
+++ b/src/fromager/sdist.py
@@ -231,7 +231,7 @@ def handle_requirement(
             ctx.wheels_downloads,
             req,
             resolved_version,
-            ctx.settings.build_tag(req.name, resolved_version, ctx.variant),
+            ctx.settings.build_tag(req.name, resolved_version),
         )
         if wheel_filename:
             logger.info(
diff --git a/src/fromager/settings.py b/src/fromager/settings.py
index 843b7fe..8de59d5 100644
--- a/src/fromager/settings.py
+++ b/src/fromager/settings.py
@@ -93,14 +93,12 @@ class Settings:
             return sdist_root_dir / relative_build_dir
         return sdist_root_dir
 
-    def build_tag(self, pkg: str, version: Version | str, variant: str) -> BuildTag:
+    def build_tag(self, pkg: str, version: Version | str) -> BuildTag:
         p = self.get_package_settings(pkg)
         changelog = p.get("changelog", {}).get(str(version), [])
-        global_changelog = self._data.get("changelog") or {}
-        global_changelog_variant = global_changelog.get(variant) or []
-        if not changelog and not global_changelog_variant:
+        if not changelog:
             return ()
-        return (len(changelog) + len(global_changelog_variant), "")
+        return (len(changelog), "")
 
     def get_package_settings(self, pkg: str) -> dict[str, dict[str, str]]:
         p = self.packages()
diff --git a/src/fromager/wheels.py b/src/fromager/wheels.py
index ae4820a..27e9fd3 100644
--- a/src/fromager/wheels.py
+++ b/src/fromager/wheels.py
@@ -200,7 +200,7 @@ def add_extra_metadata_to_wheels(
         for req_file in req_files:
             shutil.copy(req_file, dist_info_dir / f"fromager-{req_file.name}")
 
-        build_tag_from_settings = ctx.settings.build_tag(req.name, version, ctx.variant)
+        build_tag_from_settings = ctx.settings.build_tag(req.name, version)
         build_tag = build_tag_from_settings if build_tag_from_settings else (0, "")
 
         cmd = [


Test patch:
diff --git a/tests/test_settings.py b/tests/test_settings.py
index f3465cf..0915853 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -159,30 +159,10 @@ def test_changelog():
             - "rebuild xyz"
     """)
     )
-    assert s.build_tag("foo", Version("2.1.0"), "variant") == (2, "")
-    assert s.build_tag("foo", "2.1.0", "variant") == (2, "")
-    assert s.build_tag("foo", "3.1.0", "variant") == ()
-    assert s.build_tag("bar", "2.1.0", "variant") == ()
-
-
-def test_global_changelog():
-    s = _parse(
-        textwrap.dedent("""
-    changelog:
-        variant:
-            - "setuptools upgraded to 82.0.0"
-    packages:
-      foo:
-        changelog:
-          "2.1.0":
-            - "rebuild abc"
-            - "rebuild xyz"
-    """)
-    )
-    assert s.build_tag("foo", Version("2.1.0"), "variant") == (3, "")
-    assert s.build_tag("foo", Version("2.1.0"), "someother_variant") == (2, "")
-    assert s.build_tag("foo", "3.1.0", "variant") == (1, "")
-    assert s.build_tag("bar", "2.1.0", "variant") == (1, "")
+    assert s.build_tag("foo", Version("2.1.0")) == (2, "")
+    assert s.build_tag("foo", "2.1.0") == (2, "")
+    assert s.build_tag("foo", "3.1.0") == ()
+    assert s.build_tag("bar", "2.1.0") == ()
 
 
 def test_resolve_template_with_no_template():