@@ -1821,3 +1821,169 @@ def __init__(self) -> None:
18211821 )
18221822 monkeypatch .setattr (relenv .runtime .sys , "platform" , "linux" , raising = False )
18231823 assert relenv .runtime .load_openssl_provider ("default" ) == 456
1824+
1825+
1826+ def test_sysconfig_wrapper_applied_for_python_313_plus (
1827+ monkeypatch : pytest .MonkeyPatch ,
1828+ ) -> None :
1829+ """
1830+ Test that sysconfig wrapper is applied for Python 3.13+.
1831+
1832+ This is a regression test for Python 3.13 where sysconfig changed from
1833+ a single module to a package. The RelenvImporter no longer intercepts
1834+ the import automatically, so we must manually apply the wrapper.
1835+
1836+ Without this fix, Python 3.13+ would use the toolchain gcc with full path
1837+ even when RELENV_BUILDENV is not set, causing build failures with packages
1838+ like mysqlclient that compile native extensions.
1839+ """
1840+ # Simulate Python 3.13+
1841+ fake_version = (3 , 13 , 0 , "final" , 0 )
1842+ monkeypatch .setattr (relenv .runtime .sys , "version_info" , fake_version )
1843+
1844+ # Track whether wrap_sysconfig was called
1845+ wrap_called = {"count" : 0 , "module_name" : None }
1846+
1847+ def fake_wrap_sysconfig (name : str ) -> ModuleType :
1848+ wrap_called ["count" ] += 1
1849+ wrap_called ["module_name" ] = name
1850+ return ModuleType ("sysconfig" )
1851+
1852+ monkeypatch .setattr (relenv .runtime , "wrap_sysconfig" , fake_wrap_sysconfig )
1853+
1854+ # Mock other dependencies to avoid side effects
1855+ monkeypatch .setattr (relenv .runtime , "relenv_root" , lambda : pathlib .Path ("/root" ))
1856+ monkeypatch .setattr (relenv .runtime , "setup_openssl" , lambda : None )
1857+ monkeypatch .setattr (relenv .runtime , "setup_crossroot" , lambda : None )
1858+ monkeypatch .setattr (relenv .runtime , "install_cargo_config" , lambda : None )
1859+ monkeypatch .setattr (
1860+ relenv .runtime .site , "execsitecustomize" , lambda : None , raising = False
1861+ )
1862+ monkeypatch .setattr (
1863+ relenv .runtime , "wrapsitecustomize" , lambda func : func , raising = False
1864+ )
1865+
1866+ # Mock importer
1867+ fake_importer = SimpleNamespace ()
1868+ monkeypatch .setattr (relenv .runtime , "importer" , fake_importer , raising = False )
1869+
1870+ # Clear sys.meta_path to avoid side effects
1871+ original_meta_path = sys .meta_path .copy ()
1872+ monkeypatch .setattr (sys , "meta_path" , [])
1873+
1874+ try :
1875+ # Execute the module initialization code at the end of runtime.py
1876+ # This simulates what happens when the runtime module is imported
1877+ exec (
1878+ """
1879+ import sys
1880+ sys.RELENV = relenv_root()
1881+ setup_openssl()
1882+ site.execsitecustomize = wrapsitecustomize(site.execsitecustomize)
1883+ setup_crossroot()
1884+ install_cargo_config()
1885+ sys.meta_path = [importer] + sys.meta_path
1886+
1887+ # For Python 3.13+, sysconfig became a package so the importer doesn't
1888+ # intercept it. Manually wrap it here.
1889+ if sys.version_info >= (3, 13):
1890+ wrap_sysconfig("sysconfig")
1891+ """ ,
1892+ {
1893+ "sys" : relenv .runtime .sys ,
1894+ "relenv_root" : relenv .runtime .relenv_root ,
1895+ "setup_openssl" : relenv .runtime .setup_openssl ,
1896+ "site" : relenv .runtime .site ,
1897+ "wrapsitecustomize" : relenv .runtime .wrapsitecustomize ,
1898+ "setup_crossroot" : relenv .runtime .setup_crossroot ,
1899+ "install_cargo_config" : relenv .runtime .install_cargo_config ,
1900+ "importer" : fake_importer ,
1901+ "wrap_sysconfig" : fake_wrap_sysconfig ,
1902+ },
1903+ )
1904+
1905+ # Verify wrap_sysconfig was called for Python 3.13+
1906+ assert wrap_called ["count" ] == 1
1907+ assert wrap_called ["module_name" ] == "sysconfig"
1908+
1909+ finally :
1910+ # Restore original meta_path
1911+ monkeypatch .setattr (sys , "meta_path" , original_meta_path )
1912+
1913+
1914+ def test_sysconfig_wrapper_not_applied_for_python_312 (
1915+ monkeypatch : pytest .MonkeyPatch ,
1916+ ) -> None :
1917+ """
1918+ Test that sysconfig wrapper is NOT applied for Python 3.12 and earlier.
1919+
1920+ For Python 3.12 and earlier, sysconfig is a single module file and the
1921+ RelenvImporter intercepts it automatically. We should not manually wrap
1922+ it to avoid double-wrapping.
1923+ """
1924+ # Simulate Python 3.12
1925+ fake_version = (3 , 12 , 0 , "final" , 0 )
1926+ monkeypatch .setattr (relenv .runtime .sys , "version_info" , fake_version )
1927+
1928+ # Track whether wrap_sysconfig was called
1929+ wrap_called = {"count" : 0 }
1930+
1931+ def fake_wrap_sysconfig (name : str ) -> ModuleType :
1932+ wrap_called ["count" ] += 1
1933+ return ModuleType ("sysconfig" )
1934+
1935+ monkeypatch .setattr (relenv .runtime , "wrap_sysconfig" , fake_wrap_sysconfig )
1936+
1937+ # Mock other dependencies
1938+ monkeypatch .setattr (relenv .runtime , "relenv_root" , lambda : pathlib .Path ("/root" ))
1939+ monkeypatch .setattr (relenv .runtime , "setup_openssl" , lambda : None )
1940+ monkeypatch .setattr (relenv .runtime , "setup_crossroot" , lambda : None )
1941+ monkeypatch .setattr (relenv .runtime , "install_cargo_config" , lambda : None )
1942+ monkeypatch .setattr (
1943+ relenv .runtime .site , "execsitecustomize" , lambda : None , raising = False
1944+ )
1945+ monkeypatch .setattr (
1946+ relenv .runtime , "wrapsitecustomize" , lambda func : func , raising = False
1947+ )
1948+
1949+ fake_importer = SimpleNamespace ()
1950+ monkeypatch .setattr (relenv .runtime , "importer" , fake_importer , raising = False )
1951+
1952+ original_meta_path = sys .meta_path .copy ()
1953+ monkeypatch .setattr (sys , "meta_path" , [])
1954+
1955+ try :
1956+ # Execute the module initialization code
1957+ exec (
1958+ """
1959+ import sys
1960+ sys.RELENV = relenv_root()
1961+ setup_openssl()
1962+ site.execsitecustomize = wrapsitecustomize(site.execsitecustomize)
1963+ setup_crossroot()
1964+ install_cargo_config()
1965+ sys.meta_path = [importer] + sys.meta_path
1966+
1967+ # For Python 3.13+, sysconfig became a package so the importer doesn't
1968+ # intercept it. Manually wrap it here.
1969+ if sys.version_info >= (3, 13):
1970+ wrap_sysconfig("sysconfig")
1971+ """ ,
1972+ {
1973+ "sys" : relenv .runtime .sys ,
1974+ "relenv_root" : relenv .runtime .relenv_root ,
1975+ "setup_openssl" : relenv .runtime .setup_openssl ,
1976+ "site" : relenv .runtime .site ,
1977+ "wrapsitecustomize" : relenv .runtime .wrapsitecustomize ,
1978+ "setup_crossroot" : relenv .runtime .setup_crossroot ,
1979+ "install_cargo_config" : relenv .runtime .install_cargo_config ,
1980+ "importer" : fake_importer ,
1981+ "wrap_sysconfig" : fake_wrap_sysconfig ,
1982+ },
1983+ )
1984+
1985+ # Verify wrap_sysconfig was NOT called for Python 3.12
1986+ assert wrap_called ["count" ] == 0
1987+
1988+ finally :
1989+ monkeypatch .setattr (sys , "meta_path" , original_meta_path )
0 commit comments