@@ -1833,6 +1833,123 @@ def write_unversioned_report(self, output_path: Path) -> None:
18331833
18341834 print (f"✓ Written { len (unversioned )} unversioned dependencies to { output_path } " )
18351835
1836+ def normalize_dependency_name (self , name : str ) -> str :
1837+ """
1838+ Normalize dependency names to detect the same dependency referred to differently.
1839+
1840+ Examples:
1841+ - torch, pytorch, PyTorch -> pytorch
1842+ - tensorflow, TensorFlow -> tensorflow
1843+ - numpy, NumPy -> numpy
1844+
1845+ Note: This is intentionally conservative to avoid false positives.
1846+ Only normalizes well-known dependencies with common naming variations.
1847+ """
1848+ # Convert to lowercase for comparison
1849+ name_lower = name .lower ()
1850+
1851+ # Common normalization rules (ordered by specificity to avoid false matches)
1852+ normalizations = {
1853+ "tensorrt-llm" : "tensorrt-llm" ,
1854+ "trtllm" : "tensorrt-llm" ,
1855+ "tensorrt" : "tensorrt" ,
1856+ "pytorch" : "pytorch" ,
1857+ "torch" : "pytorch" ,
1858+ "tensorflow" : "tensorflow" ,
1859+ "cuda" : "cuda" ,
1860+ "cudnn" : "cudnn" ,
1861+ "nccl" : "nccl" ,
1862+ "nixl" : "nixl" ,
1863+ }
1864+
1865+ # Check if name matches any normalization rules (exact or starts with)
1866+ for key , normalized in normalizations .items ():
1867+ if name_lower == key or name_lower .startswith (key + " " ):
1868+ return normalized
1869+
1870+ # Default: return the lowercase name unchanged
1871+ # This avoids false positives from overly broad matching
1872+ return name_lower .strip ()
1873+
1874+ def detect_version_discrepancies (self ) -> List [Dict [str , any ]]:
1875+ """
1876+ Detect dependencies that appear multiple times with different versions.
1877+
1878+ Returns:
1879+ List of dictionaries containing discrepancy information:
1880+ - dependency_name: The normalized dependency name
1881+ - instances: List of {version, source_file, component} for each occurrence
1882+ """
1883+ # Group dependencies by normalized name
1884+ dependency_groups = {}
1885+
1886+ for dep in self .dependencies :
1887+ normalized_name = self .normalize_dependency_name (dep ["Dependency Name" ])
1888+
1889+ # Skip unversioned dependencies for discrepancy detection
1890+ if dep ["Version" ] in ["unspecified" , "N/A" , "" , "latest" ]:
1891+ continue
1892+
1893+ if normalized_name not in dependency_groups :
1894+ dependency_groups [normalized_name ] = []
1895+
1896+ dependency_groups [normalized_name ].append ({
1897+ "original_name" : dep ["Dependency Name" ],
1898+ "version" : dep ["Version" ],
1899+ "source_file" : dep ["Source File" ],
1900+ "component" : dep ["Component" ],
1901+ "category" : dep ["Category" ],
1902+ "critical" : dep ["Critical" ] == "Yes" ,
1903+ })
1904+
1905+ # Detect discrepancies: same normalized name with different versions
1906+ discrepancies = []
1907+
1908+ for normalized_name , instances in dependency_groups .items ():
1909+ # Get unique versions
1910+ versions = set (inst ["version" ] for inst in instances )
1911+
1912+ # If multiple versions exist, it's a discrepancy
1913+ if len (versions ) > 1 :
1914+ discrepancies .append ({
1915+ "normalized_name" : normalized_name ,
1916+ "versions" : sorted (versions ),
1917+ "instances" : instances ,
1918+ "is_critical" : any (inst ["critical" ] for inst in instances ),
1919+ })
1920+
1921+ return discrepancies
1922+
1923+ def _output_github_warnings (self , discrepancies : List [Dict [str , any ]]) -> None :
1924+ """
1925+ Output GitHub Actions warning annotations for version discrepancies.
1926+
1927+ This uses the GitHub Actions workflow command format:
1928+ ::warning file={file},line={line}::{message}
1929+
1930+ See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions
1931+ """
1932+ for disc in discrepancies :
1933+ normalized_name = disc ["normalized_name" ]
1934+ versions = disc ["versions" ]
1935+ is_critical = disc ["is_critical" ]
1936+ instances = disc ["instances" ]
1937+
1938+ # Create a concise message for the annotation
1939+ critical_prefix = "[CRITICAL] " if is_critical else ""
1940+ versions_str = ", " .join (versions )
1941+
1942+ # Output a warning for each source file where the dependency appears
1943+ for inst in instances :
1944+ message = (
1945+ f"{ critical_prefix } Version discrepancy detected for '{ normalized_name } ': "
1946+ f"found { inst ['version' ]} here, but also appears as { versions_str } elsewhere"
1947+ )
1948+
1949+ # Output GitHub Actions warning annotation
1950+ # Format: ::warning file={name}::{message}
1951+ print (f"::warning file={ inst ['source_file' ]} ::{ message } " )
1952+
18361953 def print_summary (self ) -> None :
18371954 """Print comprehensive summary statistics."""
18381955 components = {}
@@ -1919,6 +2036,47 @@ def print_summary(self) -> None:
19192036 else :
19202037 print ("\n ✓ All dependencies have version specifiers" )
19212038
2039+ # Check for version discrepancies
2040+ discrepancies = self .detect_version_discrepancies ()
2041+ if discrepancies :
2042+ print (
2043+ f"\n ⚠️ WARNING: Found { len (discrepancies )} dependencies with version discrepancies!"
2044+ )
2045+ print ("\n Dependencies pinned at different versions across the repo:" )
2046+
2047+ for disc in discrepancies [:10 ]: # Show first 10
2048+ critical_flag = " [CRITICAL]" if disc ["is_critical" ] else ""
2049+ print (f"\n • { disc ['normalized_name' ]} { critical_flag } " )
2050+ print (f" Versions found: { ', ' .join (disc ['versions' ])} " )
2051+ print (f" Locations:" )
2052+
2053+ for inst in disc ["instances" ][:5 ]: # Show first 5 instances
2054+ print (
2055+ f" - { inst ['version' ]:15s} in { inst ['component' ]:10s} "
2056+ f"({ inst ['source_file' ]} )"
2057+ )
2058+
2059+ if len (disc ["instances" ]) > 5 :
2060+ print (f" ... and { len (disc ['instances' ]) - 5 } more locations" )
2061+
2062+ if len (discrepancies ) > 10 :
2063+ print (f"\n ... and { len (discrepancies ) - 10 } more discrepancies" )
2064+
2065+ print ("\n 💡 Tip: Version discrepancies can cause:" )
2066+ print (" - Runtime conflicts and crashes" )
2067+ print (" - Unexpected behavior differences between components" )
2068+ print (" - Build failures due to incompatible APIs" )
2069+ print (" - Security vulnerabilities if older versions are used" )
2070+ print (
2071+ "\n Consider standardizing versions across the repo or documenting why "
2072+ "differences are necessary."
2073+ )
2074+
2075+ # Output GitHub Actions warnings for CI visibility
2076+ self ._output_github_warnings (discrepancies )
2077+ else :
2078+ print ("\n ✓ No version discrepancies detected" )
2079+
19222080 # Check against baseline and warn if exceeded
19232081 if total_deps > self .baseline_count :
19242082 increase = total_deps - self .baseline_count
0 commit comments