From 27627399e0792608bf5249d79c6490e8c77864e1 Mon Sep 17 00:00:00 2001
From: Randy Eckenrode <randy@largeandhighquality.com>
Date: Thu, 9 Apr 2026 21:12:36 -0400
Subject: [PATCH] Support-setting-an-upper-bound-on-versions

---
 availability | 103 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 65 insertions(+), 38 deletions(-)

diff --git a/availability b/availability
index 1abd711..bbe9a77 100755
--- a/availability
+++ b/availability
@@ -20,12 +20,34 @@ if sys.version_info < MIN_PYTHON:
 
 avVersion = "Local"
 
+
+def parse_version(ver):
+    if hasattr(ver, "string"):
+        ver = ver.string()
+
+    return (tuple(map(int, ver.split("."))) + (0, 0))[:3]
+
+
+def version_older_or_equal(lhs, rhs):
+    if not rhs:
+        return True
+
+    lhs_major, lhs_minor, lhs_patch = parse_version(lhs)
+    rhs_major, rhs_minor, rhs_patch = parse_version(rhs)
+
+    return (
+        lhs_major < rhs_major
+        or (lhs_major == rhs_major and lhs_minor < rhs_minor)
+        or (lhs_major == rhs_major and lhs_minor == rhs_minor and lhs_patch <= rhs_patch)
+    )  
+     
+
 # The build script will embed the DSL content here, otherwise we build it at runtime
 dslContent = None
 # @@INSERT_DSL_CONTENT()@@
 
 class VersionSetDSL:
-    def __init__(self, data):   self.parsedDSL = self.Parser(data)
+    def __init__(self, data, threshold):   self.parsedDSL = self.Parser(data, threshold)
     def sets(self):             return self.parsedDSL.version_sets
     def platforms(self):        return self.parsedDSL.platforms
 
@@ -111,16 +133,18 @@ class VersionSetDSL:
                 self.availability_deprecation_define_name = optionals["availability_deprecation_define_name"]
             if "version_define_name" in optionals:
                 self.availability_define_prefix = f"__{optionals['version_define_name']}_"
-        def add_version(self, version):
+        def add_version(self, version, threshold):
             if (len(self.versions) > 0) and (self.versions[-1] > version):
                 print(f"Out of order version {version} for platform {self.name}")
                 exit(-1)
-            self.versions.append(version)
+            if version_older_or_equal(version, threshold):
+                self.versions.append(version)
         def add_variant(self, variant): return self.variants.append(variant);
     class Parser:
         platforms       = {}
         version_sets    = []
-        def __init__(self, data):
+        def __init__(self, data, threshold):
+            self.threshold = threshold
             for line in data.splitlines():
                 line = line.strip().split('#',1)[0]
                 if not line:
@@ -140,7 +164,7 @@ class VersionSetDSL:
         def set(self, name, version, uversion):
             platforms = {}
             for (platformName, platform) in self.platforms.items():
-                if platform.versioned:
+                if platform.versioned and platform.versions:
                     if platform.midVersionAlias:
                         platforms[platformName] = platform.versions[-2]
                     else:
@@ -159,13 +183,13 @@ class VersionSetDSL:
                     self.platforms[platform].midVersionAlias = True
                     aliasVersion = VersionSetDSL.Version(optionals["alias_version"])
                     if aliasVersion > version:
-                        self.platforms[platform].add_version(version)
-                        self.platforms[platform].add_version(aliasVersion)
+                        self.platforms[platform].add_version(version, self.threshold)
+                        self.platforms[platform].add_version(aliasVersion, self.threshold)
                     else:
-                        self.platforms[platform].add_version(aliasVersion)
-                        self.platforms[platform].add_version(version)
+                        self.platforms[platform].add_version(aliasVersion, self.threshold)
+                        self.platforms[platform].add_version(version, self.threshold)
                 else:
-                    self.platforms[platform].add_version(version)
+                    self.platforms[platform].add_version(version, self.threshold)
             else:
                 print(f"Unknown platform \"{platform}\"")
                 exit(-1)
@@ -192,9 +216,8 @@ if not dslContent:
             parts = line.split()
             if uversion and parts and parts[0] == "set" and parts[3] == uversion:
                 break
-versions = VersionSetDSL(dslContent)
 
-def print_sets():
+def print_sets(versions):
     print("---")
     for set in versions.sets():
         print(f'{set["name"]}:')
@@ -205,7 +228,8 @@ def print_versions(platform):
     print(" ".join([version.string() for version in versions.platforms()[platform].versions]))
 
 class Preprocessor:
-    def __init__(self, inputFile, outputFile):
+    def __init__(self, versions, inputFile, outputFile):
+        self.versions = versions
         bufferedOutput = ""
         with tempfile.NamedTemporaryFile('w') as tmp:
             with open(inputFile, 'r') as input:
@@ -234,10 +258,10 @@ class Preprocessor:
         output.write("\"\"\"\n")
     def VERSION_MAP(self, output):
         sets = []
-        for set in versions.sets():
+        for set in self.versions.sets():
             set_string = ", ".join(sorted({".{} = {}".format(os,osVersion.hex()) for (os,osVersion) in set["platforms"].items()}))
             sets.append("\t{{ .set = {}, {} }}".format(set["version"].hex(), set_string))
-        platform_string = "\n".join(["    uint32_t {} = 0;".format(name) for name in versions.platforms().keys()])  
+        platform_string = "\n".join(["    uint32_t {} = 0;".format(name) for name in self.versions.platforms().keys()])
         output.write("""
 #include <set>
 #include <array>
@@ -256,16 +280,16 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
 }};
 """.format(platform_string, len(sets), ",\n".join(sets)))
     def DYLD_HEADER_VERSIONS(self, output):
-        for (name,platform) in versions.platforms().items():
+        for (name,platform) in self.versions.platforms().items():
             for version in platform.versions:
                 output.write(f"#define {platform.dyld_version_define_name + version.symbol() : <48}{version.hex()}\n");
             output.write("\n")
-        for set in versions.sets():
+        for set in self.versions.sets():
             set_string = " / ".join(sorted({"{} {}".format(os,osVersion.string()) for(os,osVersion) in set["platforms"].items()}))
             output.write("// dyld_{}_os_versions => {}\n".format(set["name"], set_string))
             output.write("#define dyld_{}_os_versions".format(set["name"]).ljust(56, ' '))
             output.write("({{ (dyld_build_version_t){{0xffffffff, {}}}; }})\n\n".format(set["version"].hex()))
-        for (name,platform) in versions.platforms().items():
+        for (name,platform) in self.versions.platforms().items():
             for version in platform.versions:
                 output.write("#define dyld_platform_version_{}_{}".format(platform.stylized_name, version.symbol()).ljust(56, ' '))
                 output.write("({{ (dyld_build_version_t){{{}, {}{}}}; }})\n".format(platform.platform_define, platform.dyld_version_define_name, version.symbol()))
@@ -274,14 +298,14 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
     def ALIAS_VERSION_MACROS(self, output, platformString, newName, oldName, **optionals):
         minVersion =  literal_eval(optionals.get("minVersion", "0x00000000"))
         maxVersion =  literal_eval(optionals.get("maxVersion", "0xFFFFFFFF"))
-        platform = versions.platforms()[platformString];
+        platform = self.versions.platforms()[platformString];
         for version in platform.versions:
             if literal_eval(version.hex()) < minVersion: continue
             if literal_eval(version.hex()) >= maxVersion: continue
             output.write(f'#define {newName + version.symbol() : <48} {oldName + version.symbol()}\n')
     def AVAILABILITY_DEFINES(self, output):
-        for platformString in versions.platforms():
-            platform = versions.platforms()[platformString];
+        for platformString in self.versions.platforms():
+            platform = self.versions.platforms()[platformString];
             if platform.bleached:
                 output.write(f"#ifndef __APPLE_BLEACH_SDK__\n")
             output.write(f"#ifndef __API_TO_BE_DEPRECATED_{platform.availability_deprecation_define_name}\n")
@@ -295,16 +319,16 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
                 output.write(f"#endif /* __APPLE_BLEACH_SDK__ */\n")
             output.write(f"\n");
     def AVAILABILITY_VERSION_DEFINES(self, output):
-        for platformString in versions.platforms():
-            short = platform = versions.platforms()[platformString].short_version_numbers
-            platform = versions.platforms()[platformString];
+        for platformString in self.versions.platforms():
+            short = platform = self.versions.platforms()[platformString].short_version_numbers
+            platform = self.versions.platforms()[platformString];
             for version in platform.versions:
                 output.write(f"#define {platform.availability_define_prefix + version.symbol() : <48}{version.decimal(short)}\n")
             output.write(f"/* {platform.availability_define_prefix}_NA is not defined to a value but is used as a token by macros to indicate that the API is unavailable */\n\n")
     def AVAILABILITY_MIN_MAX_DEFINES(self, output):
-        for platformString in versions.platforms():
-            platform = versions.platforms()[platformString];
-            if not platform.versioned:
+        for platformString in self.versions.platforms():
+            platform = self.versions.platforms()[platformString];
+            if not platform.versioned or not platform.versions:
                 continue
             if platform.bleached:
                 output.write(f"#ifndef __APPLE_BLEACH_SDK__\n")
@@ -340,8 +364,8 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
             output.write(f"   #define __API_UNAVAILABLE_PLATFORM_{displayName} {realName},unavailable\n")
         output.write(f"#if defined(__has_feature) && defined(__has_attribute)\n")
         output.write(f" #if __has_attribute(availability)\n")
-        for platformString in versions.platforms():
-            platform = versions.platforms()[platformString];
+        for platformString in self.versions.platforms():
+            platform = self.versions.platforms()[platformString];
             if platform.bleached:
                 output.write(f"#ifndef __APPLE_BLEACH_SDK__\n")
             writeDefines(platformString, platformString, platform.versioned)
@@ -357,9 +381,9 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
         output.write(f"#endif /* defined(__has_feature) && defined(__has_attribute) */\n")
     def AVAILABILITY_MACRO_IMPL(self, output, prefix, dispatcher, **optionals):
         av_version_hash = zlib.adler32(avVersion.encode()) # This does not need to cryptographically secure as it is meant to detect accidental failures
-        count = len(versions.platforms())
-        for platformString in versions.platforms():
-            platform = versions.platforms()[platformString]
+        count = len(self.versions.platforms())
+        for platformString in self.versions.platforms():
+            platform = self.versions.platforms()[platformString]
             count = count + len(platform.variants)
         platformList    = []
         argList         = []
@@ -376,9 +400,9 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
         scoped_availablity = False
         if "scoped_availablity" in optionals and optionals["scoped_availablity"] == "TRUE":
             scoped_availablity=True
-        count = len(versions.platforms())
-        for platformString in versions.platforms():
-            platform = versions.platforms()[platformString]
+        count = len(self.versions.platforms())
+        for platformString in self.versions.platforms():
+            platform = self.versions.platforms()[platformString]
             count = count + len(platform.variants)
         argList = ','.join([f'{macroName}{x}' for x in reversed(range(0, count))])
         if "argCount" in optionals:
@@ -400,8 +424,9 @@ static const std::array<VersionSetEntry, {}> sVersionMap = {{{{
         output.write(f"#endif /* __AVAILABILITY_VERSIONS_VERSION_HASH */\n")
 
 parser = argparse.ArgumentParser()
+parser.add_argument("--threshold",                          default=False,                      help='Specifies the maximum version (inclusive) included in pre-processed headers')
 group = parser.add_mutually_exclusive_group()
-for (name, platform) in versions.platforms().items():
+for (name, platform) in VersionSetDSL(dslContent, threshold=None).platforms().items():
     group.add_argument("--{}".format(name), default=False, action='store_true', help="Prints all SDK versions defined for {}".format(name))
     for alias in platform.cmd_aliases:
         group.add_argument("--{}".format(alias), dest=name, default=False, action='store_true', help="Alias for --{}".format(name))
@@ -414,8 +439,10 @@ if args.av_version:
     avVersion = args.av_version
 
 
-if args.sets:                                   print_sets();
-elif args.preprocess:                           Preprocessor(args.preprocess[0], args.preprocess[1]);
+versions = VersionSetDSL(dslContent, threshold=args.threshold)
+
+if args.sets:                                   print_sets(versions);
+elif args.preprocess:                           Preprocessor(versions, args.preprocess[0], args.preprocess[1]);
 else:
     for platform in versions.platforms().keys():
         if getattr(args, platform, None):
-- 
2.51.2

