Package: bind9-doc
Version: 1:9.19.21-1
Severity: wishlist
Tags: patch upstream
User: reproducible-bui...@lists.alioth.debian.org
Usertags: randomness

Dear Maintainer,

I'm an occasional volunteer contributor to the Reproducible Builds[1] project,
and noticed recently that the bind9-doc package failed[2] an automated Debian
package build test.

>From inspecting the differences in the generated documentation, it appears that
some tag annotations (example[3]) in the source documentation are de-duplicated
in a non-deterministic manner during Sphinx documentation builds.

The cause is that the de-duplication is performed using a non-order-preserving
Python set[4] object, meaning that the resulting output documentation package
can vary at build-time.

Please find attached a patch that implements order-preserving de-duplication
of these tags.  I've confirmed that the package compiles with it applied, and
believe that it should produce deterministic tag output in the built package.

Regards,
James

[1] - https://reproducible-builds.org/

[2] - 
https://tests.reproducible-builds.org/debian/rb-pkg/bookworm/amd64/diffoscope-results/bind9.html

[3] - 
https://sources.debian.org/src/bind9/1%3A9.18.19-1~deb12u1/doc/arm/reference.rst/#L5648

[4] - https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
diff --git a/doc/arm/_ext/iscconf.py b/doc/arm/_ext/iscconf.py
index b5bd966e2..0b96d7bd3 100644
--- a/doc/arm/_ext/iscconf.py
+++ b/doc/arm/_ext/iscconf.py
@@ -41,12 +41,18 @@ logger = logging.getLogger(__name__)
 
 def split_csv(argument, required):
     argument = argument or ""
-    outlist = list(filter(len, (s.strip() for s in argument.split(","))))
-    if required and not outlist:
+    values = list(filter(len, (s.strip() for s in argument.split(","))))
+    if required and not values:
         raise ValueError(
             "a non-empty list required; provide at least one value or remove"
             " this option"
         )
+    # Order-preserving de-duplication
+    outlist, seen = list(), set()
+    for value in values:
+        if value not in seen:
+            seen.add(value)
+            outlist.append(value)
     return outlist
 
 
@@ -73,10 +79,8 @@ def domain_factory(domainname, domainlabel, todolist, 
grammar):
 
         def run(self):
             placeholder = todolist("")
-            placeholder["isc_filter_tags"] = 
set(self.options.get("filter_tags", []))
-            placeholder["isc_filter_blocks"] = set(
-                self.options.get("filter_blocks", [])
-            )
+            placeholder["isc_filter_tags"] = self.options.get("filter_tags", 
[])
+            placeholder["isc_filter_blocks"] = 
self.options.get("filter_blocks", [])
             return [placeholder]
 
     class ISCConfDomain(Domain):
@@ -127,7 +131,7 @@ def domain_factory(domainname, domainlabel, todolist, 
grammar):
 
             @property
             def isc_tags(self):
-                return set(self.options.get("tags", []))
+                return self.options.get("tags", [])
 
             @property
             def isc_short(self):

Reply via email to