1import unittest 2from unittest import mock 3 4from python.private.pypi.whl_installer import wheel 5from python.private.pypi.whl_installer.platform import OS, Arch, Platform 6 7_HOST_INTERPRETER_FN = ( 8 "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version" 9) 10 11 12class DepsTest(unittest.TestCase): 13 def test_simple(self): 14 deps = wheel.Deps("foo", requires_dist=["bar"]) 15 16 got = deps.build() 17 18 self.assertIsInstance(got, wheel.FrozenDeps) 19 self.assertEqual(["bar"], got.deps) 20 self.assertEqual({}, got.deps_select) 21 22 def test_can_add_os_specific_deps(self): 23 deps = wheel.Deps( 24 "foo", 25 requires_dist=[ 26 "bar", 27 "an_osx_dep; sys_platform=='darwin'", 28 "posix_dep; os_name=='posix'", 29 "win_dep; os_name=='nt'", 30 ], 31 platforms={ 32 Platform(os=OS.linux, arch=Arch.x86_64), 33 Platform(os=OS.osx, arch=Arch.x86_64), 34 Platform(os=OS.osx, arch=Arch.aarch64), 35 Platform(os=OS.windows, arch=Arch.x86_64), 36 }, 37 ) 38 39 got = deps.build() 40 41 self.assertEqual(["bar"], got.deps) 42 self.assertEqual( 43 { 44 "@platforms//os:linux": ["posix_dep"], 45 "@platforms//os:osx": ["an_osx_dep", "posix_dep"], 46 "@platforms//os:windows": ["win_dep"], 47 }, 48 got.deps_select, 49 ) 50 51 def test_can_add_os_specific_deps_with_specific_python_version(self): 52 deps = wheel.Deps( 53 "foo", 54 requires_dist=[ 55 "bar", 56 "an_osx_dep; sys_platform=='darwin'", 57 "posix_dep; os_name=='posix'", 58 "win_dep; os_name=='nt'", 59 ], 60 platforms={ 61 Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8), 62 Platform(os=OS.osx, arch=Arch.x86_64, minor_version=8), 63 Platform(os=OS.osx, arch=Arch.aarch64, minor_version=8), 64 Platform(os=OS.windows, arch=Arch.x86_64, minor_version=8), 65 }, 66 ) 67 68 got = deps.build() 69 70 self.assertEqual(["bar"], got.deps) 71 self.assertEqual( 72 { 73 "@platforms//os:linux": ["posix_dep"], 74 "@platforms//os:osx": ["an_osx_dep", "posix_dep"], 75 "@platforms//os:windows": ["win_dep"], 76 }, 77 got.deps_select, 78 ) 79 80 def test_deps_are_added_to_more_specialized_platforms(self): 81 got = wheel.Deps( 82 "foo", 83 requires_dist=[ 84 "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'", 85 "mac_dep; sys_platform=='darwin'", 86 ], 87 platforms={ 88 Platform(os=OS.osx, arch=Arch.x86_64), 89 Platform(os=OS.osx, arch=Arch.aarch64), 90 }, 91 ).build() 92 93 self.assertEqual( 94 wheel.FrozenDeps( 95 deps=[], 96 deps_select={ 97 "osx_aarch64": ["m1_dep", "mac_dep"], 98 "@platforms//os:osx": ["mac_dep"], 99 }, 100 ), 101 got, 102 ) 103 104 def test_deps_from_more_specialized_platforms_are_propagated(self): 105 got = wheel.Deps( 106 "foo", 107 requires_dist=[ 108 "a_mac_dep; sys_platform=='darwin'", 109 "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'", 110 ], 111 platforms={ 112 Platform(os=OS.osx, arch=Arch.x86_64), 113 Platform(os=OS.osx, arch=Arch.aarch64), 114 }, 115 ).build() 116 117 self.assertEqual([], got.deps) 118 self.assertEqual( 119 { 120 "osx_aarch64": ["a_mac_dep", "m1_dep"], 121 "@platforms//os:osx": ["a_mac_dep"], 122 }, 123 got.deps_select, 124 ) 125 126 def test_non_platform_markers_are_added_to_common_deps(self): 127 got = wheel.Deps( 128 "foo", 129 requires_dist=[ 130 "bar", 131 "baz; implementation_name=='cpython'", 132 "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'", 133 ], 134 platforms={ 135 Platform(os=OS.linux, arch=Arch.x86_64), 136 Platform(os=OS.osx, arch=Arch.x86_64), 137 Platform(os=OS.osx, arch=Arch.aarch64), 138 Platform(os=OS.windows, arch=Arch.x86_64), 139 }, 140 ).build() 141 142 self.assertEqual(["bar", "baz"], got.deps) 143 self.assertEqual( 144 { 145 "osx_aarch64": ["m1_dep"], 146 }, 147 got.deps_select, 148 ) 149 150 def test_self_is_ignored(self): 151 deps = wheel.Deps( 152 "foo", 153 requires_dist=[ 154 "bar", 155 "req_dep; extra == 'requests'", 156 "foo[requests]; extra == 'ssl'", 157 "ssl_lib; extra == 'ssl'", 158 ], 159 extras={"ssl"}, 160 ) 161 162 got = deps.build() 163 164 self.assertEqual(["bar", "req_dep", "ssl_lib"], got.deps) 165 self.assertEqual({}, got.deps_select) 166 167 def test_self_dependencies_can_come_in_any_order(self): 168 deps = wheel.Deps( 169 "foo", 170 requires_dist=[ 171 "bar", 172 "baz; extra == 'feat'", 173 "foo[feat2]; extra == 'all'", 174 "foo[feat]; extra == 'feat2'", 175 "zdep; extra == 'all'", 176 ], 177 extras={"all"}, 178 ) 179 180 got = deps.build() 181 182 self.assertEqual(["bar", "baz", "zdep"], got.deps) 183 self.assertEqual({}, got.deps_select) 184 185 def test_can_get_deps_based_on_specific_python_version(self): 186 requires_dist = [ 187 "bar", 188 "baz; python_version < '3.8'", 189 "posix_dep; os_name=='posix' and python_version >= '3.8'", 190 ] 191 192 py38_deps = wheel.Deps( 193 "foo", 194 requires_dist=requires_dist, 195 platforms=[ 196 Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8), 197 ], 198 ).build() 199 py37_deps = wheel.Deps( 200 "foo", 201 requires_dist=requires_dist, 202 platforms=[ 203 Platform(os=OS.linux, arch=Arch.x86_64, minor_version=7), 204 ], 205 ).build() 206 207 self.assertEqual(["bar", "baz"], py37_deps.deps) 208 self.assertEqual({}, py37_deps.deps_select) 209 self.assertEqual(["bar"], py38_deps.deps) 210 self.assertEqual({"@platforms//os:linux": ["posix_dep"]}, py38_deps.deps_select) 211 212 @mock.patch(_HOST_INTERPRETER_FN) 213 def test_no_version_select_when_single_version(self, mock_host_interpreter_version): 214 requires_dist = [ 215 "bar", 216 "baz; python_version >= '3.8'", 217 "posix_dep; os_name=='posix'", 218 "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'", 219 "arch_dep; platform_machine=='x86_64' and python_version >= '3.8'", 220 ] 221 mock_host_interpreter_version.return_value = 7 222 223 self.maxDiff = None 224 225 deps = wheel.Deps( 226 "foo", 227 requires_dist=requires_dist, 228 platforms=[ 229 Platform(os=os, arch=Arch.x86_64, minor_version=minor) 230 for minor in [8] 231 for os in [OS.linux, OS.windows] 232 ], 233 ) 234 got = deps.build() 235 236 self.assertEqual(["bar", "baz"], got.deps) 237 self.assertEqual( 238 { 239 "@platforms//os:linux": ["posix_dep", "posix_dep_with_version"], 240 "linux_x86_64": ["arch_dep", "posix_dep", "posix_dep_with_version"], 241 "windows_x86_64": ["arch_dep"], 242 }, 243 got.deps_select, 244 ) 245 246 @mock.patch(_HOST_INTERPRETER_FN) 247 def test_can_get_version_select(self, mock_host_interpreter_version): 248 requires_dist = [ 249 "bar", 250 "baz; python_version < '3.8'", 251 "baz_new; python_version >= '3.8'", 252 "posix_dep; os_name=='posix'", 253 "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'", 254 "arch_dep; platform_machine=='x86_64' and python_version < '3.8'", 255 ] 256 mock_host_interpreter_version.return_value = 7 257 258 self.maxDiff = None 259 260 deps = wheel.Deps( 261 "foo", 262 requires_dist=requires_dist, 263 platforms=[ 264 Platform(os=os, arch=Arch.x86_64, minor_version=minor) 265 for minor in [7, 8, 9] 266 for os in [OS.linux, OS.windows] 267 ], 268 ) 269 got = deps.build() 270 271 self.assertEqual(["bar"], got.deps) 272 self.assertEqual( 273 { 274 "//conditions:default": ["baz"], 275 "@//python/config_settings:is_python_3.7": ["baz"], 276 "@//python/config_settings:is_python_3.8": ["baz_new"], 277 "@//python/config_settings:is_python_3.9": ["baz_new"], 278 "@platforms//os:linux": ["baz", "posix_dep"], 279 "cp37_linux_x86_64": ["arch_dep", "baz", "posix_dep"], 280 "cp37_windows_x86_64": ["arch_dep", "baz"], 281 "cp37_linux_anyarch": ["baz", "posix_dep"], 282 "cp38_linux_anyarch": [ 283 "baz_new", 284 "posix_dep", 285 "posix_dep_with_version", 286 ], 287 "cp39_linux_anyarch": [ 288 "baz_new", 289 "posix_dep", 290 "posix_dep_with_version", 291 ], 292 "linux_x86_64": ["arch_dep", "baz", "posix_dep"], 293 "windows_x86_64": ["arch_dep", "baz"], 294 }, 295 got.deps_select, 296 ) 297 298 @mock.patch(_HOST_INTERPRETER_FN) 299 def test_deps_spanning_all_target_py_versions_are_added_to_common( 300 self, mock_host_version 301 ): 302 requires_dist = [ 303 "bar", 304 "baz (<2,>=1.11) ; python_version < '3.8'", 305 "baz (<2,>=1.14) ; python_version >= '3.8'", 306 ] 307 mock_host_version.return_value = 8 308 309 deps = wheel.Deps( 310 "foo", 311 requires_dist=requires_dist, 312 platforms=Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]), 313 ) 314 got = deps.build() 315 316 self.assertEqual(["bar", "baz"], got.deps) 317 self.assertEqual({}, got.deps_select) 318 319 @mock.patch(_HOST_INTERPRETER_FN) 320 def test_deps_are_not_duplicated(self, mock_host_version): 321 mock_host_version.return_value = 7 322 323 # See an example in 324 # https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl.metadata 325 requires_dist = [ 326 "bar >=0.1.0 ; python_version < '3.7'", 327 "bar >=0.2.0 ; python_version >= '3.7'", 328 "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'", 329 "bar >=0.4.0 ; python_version >= '3.9'", 330 "bar >=0.5.0 ; python_version <= '3.9' and platform_system == 'Darwin' and platform_machine == 'arm64'", 331 "bar >=0.5.0 ; python_version >= '3.10' and platform_system == 'Darwin'", 332 "bar >=0.5.0 ; python_version >= '3.10'", 333 "bar >=0.6.0 ; python_version >= '3.11'", 334 ] 335 336 deps = wheel.Deps( 337 "foo", 338 requires_dist=requires_dist, 339 platforms=Platform.from_string(["cp37_*", "cp310_*"]), 340 ) 341 got = deps.build() 342 343 self.assertEqual(["bar"], got.deps) 344 self.assertEqual({}, got.deps_select) 345 346 @mock.patch(_HOST_INTERPRETER_FN) 347 def test_deps_are_not_duplicated_when_encountering_platform_dep_first( 348 self, mock_host_version 349 ): 350 mock_host_version.return_value = 7 351 352 # Note, that we are sorting the incoming `requires_dist` and we need to ensure that we are not getting any 353 # issues even if the platform-specific line comes first. 354 requires_dist = [ 355 "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'", 356 "bar >=0.5.0 ; python_version >= '3.9'", 357 ] 358 359 deps = wheel.Deps( 360 "foo", 361 requires_dist=requires_dist, 362 platforms=Platform.from_string(["cp37_*", "cp310_*"]), 363 ) 364 got = deps.build() 365 366 self.assertEqual(["bar"], got.deps) 367 self.assertEqual({}, got.deps_select) 368 369 370if __name__ == "__main__": 371 unittest.main() 372