1# Copyright 2018 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import platform 17import subprocess 18import unittest 19import zipfile 20 21 22class WheelTest(unittest.TestCase): 23 maxDiff = None 24 25 def test_py_library_wheel(self): 26 filename = os.path.join( 27 os.environ["TEST_SRCDIR"], 28 "rules_python", 29 "examples", 30 "wheel", 31 "example_minimal_library-0.0.1-py3-none-any.whl", 32 ) 33 with zipfile.ZipFile(filename) as zf: 34 self.assertEqual( 35 zf.namelist(), 36 [ 37 "examples/wheel/lib/module_with_data.py", 38 "examples/wheel/lib/simple_module.py", 39 "example_minimal_library-0.0.1.dist-info/WHEEL", 40 "example_minimal_library-0.0.1.dist-info/METADATA", 41 "example_minimal_library-0.0.1.dist-info/RECORD", 42 ], 43 ) 44 45 def test_py_package_wheel(self): 46 filename = os.path.join( 47 os.environ["TEST_SRCDIR"], 48 "rules_python", 49 "examples", 50 "wheel", 51 "example_minimal_package-0.0.1-py3-none-any.whl", 52 ) 53 with zipfile.ZipFile(filename) as zf: 54 self.assertEqual( 55 zf.namelist(), 56 [ 57 "examples/wheel/lib/data.txt", 58 "examples/wheel/lib/module_with_data.py", 59 "examples/wheel/lib/simple_module.py", 60 "examples/wheel/main.py", 61 "example_minimal_package-0.0.1.dist-info/WHEEL", 62 "example_minimal_package-0.0.1.dist-info/METADATA", 63 "example_minimal_package-0.0.1.dist-info/RECORD", 64 ], 65 ) 66 67 def test_customized_wheel(self): 68 filename = os.path.join( 69 os.environ["TEST_SRCDIR"], 70 "rules_python", 71 "examples", 72 "wheel", 73 "example_customized-0.0.1-py3-none-any.whl", 74 ) 75 with zipfile.ZipFile(filename) as zf: 76 self.assertEqual( 77 zf.namelist(), 78 [ 79 "examples/wheel/lib/data.txt", 80 "examples/wheel/lib/module_with_data.py", 81 "examples/wheel/lib/simple_module.py", 82 "examples/wheel/main.py", 83 "example_customized-0.0.1.dist-info/WHEEL", 84 "example_customized-0.0.1.dist-info/METADATA", 85 "example_customized-0.0.1.dist-info/entry_points.txt", 86 "example_customized-0.0.1.dist-info/NOTICE", 87 "example_customized-0.0.1.dist-info/README", 88 "example_customized-0.0.1.dist-info/RECORD", 89 ], 90 ) 91 record_contents = zf.read("example_customized-0.0.1.dist-info/RECORD") 92 wheel_contents = zf.read("example_customized-0.0.1.dist-info/WHEEL") 93 metadata_contents = zf.read("example_customized-0.0.1.dist-info/METADATA") 94 entry_point_contents = zf.read( 95 "example_customized-0.0.1.dist-info/entry_points.txt" 96 ) 97 98 self.assertEqual( 99 record_contents, 100 # The entries are guaranteed to be sorted. 101 b"""\ 102example_customized-0.0.1.dist-info/METADATA,sha256=QYQcDJFQSIqan8eiXqL67bqsUfgEAwf2hoK_Lgi1S-0,559 103example_customized-0.0.1.dist-info/NOTICE,sha256=Xpdw-FXET1IRgZ_wTkx1YQfo1-alET0FVf6V1LXO4js,76 104example_customized-0.0.1.dist-info/README,sha256=WmOFwZ3Jga1bHG3JiGRsUheb4UbLffUxyTdHczS27-o,40 105example_customized-0.0.1.dist-info/RECORD,, 106example_customized-0.0.1.dist-info/WHEEL,sha256=sobxWSyDDkdg_rinUth-jxhXHqoNqlmNMJY3aTZn2Us,91 107example_customized-0.0.1.dist-info/entry_points.txt,sha256=pqzpbQ8MMorrJ3Jp0ntmpZcuvfByyqzMXXi2UujuXD0,137 108examples/wheel/lib/data.txt,sha256=9vJKEdfLu8bZRArKLroPZJh1XKkK3qFMXiM79MBL2Sg,12 109examples/wheel/lib/module_with_data.py,sha256=8s0Khhcqz3yVsBKv2IB5u4l4TMKh7-c_V6p65WVHPms,637 110examples/wheel/lib/simple_module.py,sha256=z2hwciab_XPNIBNH8B1Q5fYgnJvQTeYf0ZQJpY8yLLY,637 111examples/wheel/main.py,sha256=sgg5iWN_9inYBjm6_Zw27hYdmo-l24fA-2rfphT-IlY,909 112""", 113 ) 114 self.assertEqual( 115 wheel_contents, 116 b"""\ 117Wheel-Version: 1.0 118Generator: bazel-wheelmaker 1.0 119Root-Is-Purelib: true 120Tag: py3-none-any 121""", 122 ) 123 self.assertEqual( 124 metadata_contents, 125 b"""\ 126Metadata-Version: 2.1 127Name: example_customized 128Author: Example Author with non-ascii characters: \xc5\xbc\xc3\xb3\xc5\x82w 129Author-email: example@example.com 130Home-page: www.example.com 131License: Apache 2.0 132Description-Content-Type: text/markdown 133Summary: A one-line summary of this test package 134Project-URL: Bug Tracker, www.example.com/issues 135Project-URL: Documentation, www.example.com/docs 136Classifier: License :: OSI Approved :: Apache Software License 137Classifier: Intended Audience :: Developers 138Requires-Dist: pytest 139Version: 0.0.1 140 141This is a sample description of a wheel. 142""", 143 ) 144 self.assertEqual( 145 entry_point_contents, 146 b"""\ 147[console_scripts] 148another = foo.bar:baz 149customized_wheel = examples.wheel.main:main 150 151[group2] 152first = first.main:f 153second = second.main:s""", 154 ) 155 156 def test_filename_escaping(self): 157 filename = os.path.join( 158 os.environ["TEST_SRCDIR"], 159 "rules_python", 160 "examples", 161 "wheel", 162 "file_name_escaping-0.0.1_r7-py3-none-any.whl", 163 ) 164 with zipfile.ZipFile(filename) as zf: 165 self.assertEqual( 166 zf.namelist(), 167 [ 168 "examples/wheel/lib/data.txt", 169 "examples/wheel/lib/module_with_data.py", 170 "examples/wheel/lib/simple_module.py", 171 "examples/wheel/main.py", 172 # PEP calls for replacing only in the archive filename. 173 # Alas setuptools also escapes in the dist-info directory 174 # name, so let's be compatible. 175 "file_name_escaping-0.0.1_r7.dist-info/WHEEL", 176 "file_name_escaping-0.0.1_r7.dist-info/METADATA", 177 "file_name_escaping-0.0.1_r7.dist-info/RECORD", 178 ], 179 ) 180 metadata_contents = zf.read( 181 "file_name_escaping-0.0.1_r7.dist-info/METADATA" 182 ) 183 self.assertEqual( 184 metadata_contents, 185 b"""\ 186Metadata-Version: 2.1 187Name: file~~name-escaping 188Version: 0.0.1-r7 189 190UNKNOWN 191""", 192 ) 193 194 def test_custom_package_root_wheel(self): 195 filename = os.path.join( 196 os.environ["TEST_SRCDIR"], 197 "rules_python", 198 "examples", 199 "wheel", 200 "examples_custom_package_root-0.0.1-py3-none-any.whl", 201 ) 202 203 with zipfile.ZipFile(filename) as zf: 204 self.assertEqual( 205 zf.namelist(), 206 [ 207 "wheel/lib/data.txt", 208 "wheel/lib/module_with_data.py", 209 "wheel/lib/simple_module.py", 210 "wheel/main.py", 211 "examples_custom_package_root-0.0.1.dist-info/WHEEL", 212 "examples_custom_package_root-0.0.1.dist-info/METADATA", 213 "examples_custom_package_root-0.0.1.dist-info/entry_points.txt", 214 "examples_custom_package_root-0.0.1.dist-info/RECORD", 215 ], 216 ) 217 218 record_contents = zf.read( 219 "examples_custom_package_root-0.0.1.dist-info/RECORD" 220 ).decode("utf-8") 221 222 # Ensure RECORD files do not have leading forward slashes 223 for line in record_contents.splitlines(): 224 self.assertFalse(line.startswith("/")) 225 226 def test_custom_package_root_multi_prefix_wheel(self): 227 filename = os.path.join( 228 os.environ["TEST_SRCDIR"], 229 "rules_python", 230 "examples", 231 "wheel", 232 "example_custom_package_root_multi_prefix-0.0.1-py3-none-any.whl", 233 ) 234 235 with zipfile.ZipFile(filename) as zf: 236 self.assertEqual( 237 zf.namelist(), 238 [ 239 "data.txt", 240 "module_with_data.py", 241 "simple_module.py", 242 "main.py", 243 "example_custom_package_root_multi_prefix-0.0.1.dist-info/WHEEL", 244 "example_custom_package_root_multi_prefix-0.0.1.dist-info/METADATA", 245 "example_custom_package_root_multi_prefix-0.0.1.dist-info/RECORD", 246 ], 247 ) 248 249 record_contents = zf.read( 250 "example_custom_package_root_multi_prefix-0.0.1.dist-info/RECORD" 251 ).decode("utf-8") 252 253 # Ensure RECORD files do not have leading forward slashes 254 for line in record_contents.splitlines(): 255 self.assertFalse(line.startswith("/")) 256 257 def test_custom_package_root_multi_prefix_reverse_order_wheel(self): 258 filename = os.path.join( 259 os.environ["TEST_SRCDIR"], 260 "rules_python", 261 "examples", 262 "wheel", 263 "example_custom_package_root_multi_prefix_reverse_order-0.0.1-py3-none-any.whl", 264 ) 265 266 with zipfile.ZipFile(filename) as zf: 267 self.assertEqual( 268 zf.namelist(), 269 [ 270 "lib/data.txt", 271 "lib/module_with_data.py", 272 "lib/simple_module.py", 273 "main.py", 274 "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/WHEEL", 275 "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/METADATA", 276 "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/RECORD", 277 ], 278 ) 279 280 record_contents = zf.read( 281 "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/RECORD" 282 ).decode("utf-8") 283 284 # Ensure RECORD files do not have leading forward slashes 285 for line in record_contents.splitlines(): 286 self.assertFalse(line.startswith("/")) 287 288 def test_python_requires_wheel(self): 289 filename = os.path.join( 290 os.environ["TEST_SRCDIR"], 291 "rules_python", 292 "examples", 293 "wheel", 294 "example_python_requires_in_a_package-0.0.1-py3-none-any.whl", 295 ) 296 with zipfile.ZipFile(filename) as zf: 297 metadata_contents = zf.read( 298 "example_python_requires_in_a_package-0.0.1.dist-info/METADATA" 299 ) 300 # The entries are guaranteed to be sorted. 301 self.assertEqual( 302 metadata_contents, 303 b"""\ 304Metadata-Version: 2.1 305Name: example_python_requires_in_a_package 306Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* 307Version: 0.0.1 308 309UNKNOWN 310""", 311 ) 312 313 def test_python_abi3_binary_wheel(self): 314 arch = "amd64" 315 if platform.system() != "Windows": 316 arch = subprocess.check_output(["uname", "-m"]).strip().decode() 317 # These strings match the strings from py_wheel() in BUILD 318 os_strings = { 319 "Linux": "manylinux2014", 320 "Darwin": "macosx_11_0", 321 "Windows": "win", 322 } 323 os_string = os_strings[platform.system()] 324 filename = os.path.join( 325 os.environ["TEST_SRCDIR"], 326 "rules_python", 327 "examples", 328 "wheel", 329 f"example_python_abi3_binary_wheel-0.0.1-cp38-abi3-{os_string}_{arch}.whl", 330 ) 331 with zipfile.ZipFile(filename) as zf: 332 metadata_contents = zf.read( 333 "example_python_abi3_binary_wheel-0.0.1.dist-info/METADATA" 334 ) 335 # The entries are guaranteed to be sorted. 336 self.assertEqual( 337 metadata_contents, 338 b"""\ 339Metadata-Version: 2.1 340Name: example_python_abi3_binary_wheel 341Requires-Python: >=3.8 342Version: 0.0.1 343 344UNKNOWN 345""", 346 ) 347 wheel_contents = zf.read( 348 "example_python_abi3_binary_wheel-0.0.1.dist-info/WHEEL" 349 ) 350 self.assertEqual( 351 wheel_contents.decode(), 352 f"""\ 353Wheel-Version: 1.0 354Generator: bazel-wheelmaker 1.0 355Root-Is-Purelib: false 356Tag: cp38-abi3-{os_string}_{arch} 357""", 358 ) 359 360 def test_rule_creates_directory_and_is_included_in_wheel(self): 361 filename = os.path.join( 362 os.environ["TEST_SRCDIR"], 363 "rules_python", 364 "examples", 365 "wheel", 366 "use_rule_with_dir_in_outs-0.0.1-py3-none-any.whl", 367 ) 368 369 with zipfile.ZipFile(filename) as zf: 370 self.assertEqual( 371 zf.namelist(), 372 [ 373 "examples/wheel/main.py", 374 "examples/wheel/someDir/foo.py", 375 "use_rule_with_dir_in_outs-0.0.1.dist-info/WHEEL", 376 "use_rule_with_dir_in_outs-0.0.1.dist-info/METADATA", 377 "use_rule_with_dir_in_outs-0.0.1.dist-info/RECORD", 378 ], 379 ) 380 381 def test_rule_expands_workspace_status_keys_in_wheel_metadata(self): 382 filename = os.path.join( 383 os.environ["TEST_SRCDIR"], 384 "rules_python", 385 "examples", 386 "wheel", 387 "example_minimal_library_BUILD_USER_-0.1._BUILD_TIMESTAMP_-py3-none-any.whl", 388 ) 389 390 with zipfile.ZipFile(filename) as zf: 391 metadata_file = None 392 for f in zf.namelist(): 393 self.assertNotIn("_BUILD_TIMESTAMP_", f) 394 self.assertNotIn("_BUILD_USER_", f) 395 if os.path.basename(f) == "METADATA": 396 metadata_file = f 397 self.assertIsNotNone(metadata_file) 398 399 version = None 400 name = None 401 with zf.open(metadata_file) as fp: 402 for line in fp: 403 if line.startswith(b"Version:"): 404 version = line.decode().split()[-1] 405 if line.startswith(b"Name:"): 406 name = line.decode().split()[-1] 407 self.assertIsNotNone(version) 408 self.assertIsNotNone(name) 409 self.assertNotIn("{BUILD_TIMESTAMP}", version) 410 self.assertNotIn("{BUILD_USER}", name) 411 412 413if __name__ == "__main__": 414 unittest.main() 415