Repository: python-wheel-build/fromager

Issue:
Add global changelog to settings to trigger a rebuild of every package with a new build tag
It is difficult to identify whether a version bump is significant enough to cause a rebuild of any package that depends on it. So we will leave it to the user to update the changelog. There might be times where we need a to trigger a build of every package (eg: maybe when setuptools get upgraded to some version with a lot of changes). In this case we need a way for the user to tell fromager to rebuild everything with a new build tag. Adding a global changelog will be helpful for this

Discussion:

Patch:
diff --git a/src/fromager/commands/build.py b/src/fromager/commands/build.py
index 17ef2a6..f435690 100644
--- a/src/fromager/commands/build.py
+++ b/src/fromager/commands/build.py
@@ -173,7 +173,9 @@ 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)
+        build_tag_from_settings = wkctx.settings.build_tag(
+            req.name, dist_version, wkctx.variant
+        )
         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 1e293be..f7522ef 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.settings.build_tag(req.name, resolved_version, ctx.variant),
         )
         if wheel_filename:
             logger.info(
diff --git a/src/fromager/settings.py b/src/fromager/settings.py
index 8de59d5..843b7fe 100644
--- a/src/fromager/settings.py
+++ b/src/fromager/settings.py
@@ -93,12 +93,14 @@ class Settings:
             return sdist_root_dir / relative_build_dir
         return sdist_root_dir
 
-    def build_tag(self, pkg: str, version: Version | str) -> BuildTag:
+    def build_tag(self, pkg: str, version: Version | str, variant: str) -> BuildTag:
         p = self.get_package_settings(pkg)
         changelog = p.get("changelog", {}).get(str(version), [])
-        if not changelog:
+        global_changelog = self._data.get("changelog") or {}
+        global_changelog_variant = global_changelog.get(variant) or []
+        if not changelog and not global_changelog_variant:
             return ()
-        return (len(changelog), "")
+        return (len(changelog) + len(global_changelog_variant), "")
 
     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 27e9fd3..ae4820a 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)
+        build_tag_from_settings = ctx.settings.build_tag(req.name, version, ctx.variant)
         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 0915853..f3465cf 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -159,10 +159,30 @@ def test_changelog():
             - "rebuild xyz"
     """)
     )
-    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") == ()
+    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, "")
 
 
 def test_resolve_template_with_no_template():