diff options
Diffstat (limited to 'examples/wheel')
-rw-r--r-- | examples/wheel/BUILD.bazel | 287 | ||||
-rw-r--r-- | examples/wheel/NOTICE | 1 | ||||
-rw-r--r-- | examples/wheel/README.md | 1 | ||||
-rw-r--r-- | examples/wheel/lib/BUILD.bazel | 36 | ||||
-rw-r--r-- | examples/wheel/lib/module_with_data.py | 17 | ||||
-rw-r--r-- | examples/wheel/lib/simple_module.py | 17 | ||||
-rw-r--r-- | examples/wheel/main.py | 30 | ||||
-rw-r--r-- | examples/wheel/private/BUILD.bazel | 7 | ||||
-rw-r--r-- | examples/wheel/private/directory_writer.py | 58 | ||||
-rw-r--r-- | examples/wheel/private/wheel_utils.bzl | 73 | ||||
-rw-r--r-- | examples/wheel/wheel_test.py | 414 |
11 files changed, 941 insertions, 0 deletions
diff --git a/examples/wheel/BUILD.bazel b/examples/wheel/BUILD.bazel new file mode 100644 index 0000000..f56a41b --- /dev/null +++ b/examples/wheel/BUILD.bazel @@ -0,0 +1,287 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("//examples/wheel/private:wheel_utils.bzl", "directory_writer", "make_variable_tags") +load("//python:defs.bzl", "py_library", "py_test") +load("//python:packaging.bzl", "py_package", "py_wheel") +load("//python:versions.bzl", "gen_python_config_settings") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +py_library( + name = "main", + srcs = ["main.py"], + deps = [ + "//examples/wheel/lib:simple_module", + "//examples/wheel/lib:module_with_data", + # Example dependency which is not packaged in the wheel + # due to "packages" filter on py_package rule. + "//tests/load_from_macro:foo", + ], +) + +py_library( + name = "main_with_gen_data", + srcs = ["main.py"], + data = [ + ":gen_dir", + ], +) + +directory_writer( + name = "gen_dir", + out = "someDir", + files = {"foo.py": ""}, +) + +# Package just a specific py_libraries, without their dependencies +py_wheel( + name = "minimal_with_py_library", + testonly = True, # Set this to verify the generated .dist target doesn't break things + # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl" + distribution = "example_minimal_library", + python_tag = "py3", + version = "0.0.1", + deps = [ + "//examples/wheel/lib:module_with_data", + "//examples/wheel/lib:simple_module", + ], +) + +# Populate a rule with "Make Variable" arguments for +# abi, python_tag and version. You might want to do this +# for the following use cases: +# - abi, python_tag: introspect a toolchain to map to appropriate cpython tags +# - version: populate given this or a dependent module's version +make_variable_tags( + name = "make_variable_tags", +) + +py_wheel( + name = "minimal_with_py_library_with_make_variables", + testonly = True, + abi = "$(ABI)", + distribution = "example_minimal_library", + python_tag = "$(PYTHON_TAG)", + toolchains = ["//examples/wheel:make_variable_tags"], + version = "$(VERSION)", + deps = [ + "//examples/wheel/lib:module_with_data", + "//examples/wheel/lib:simple_module", + ], +) + +build_test( + name = "dist_build_tests", + targets = [":minimal_with_py_library.dist"], +) + +# Package just a specific py_libraries, without their dependencies +py_wheel( + name = "minimal_with_py_library_with_stamp", + # Package data. We're building "example_minimal_library-0.0.1-py3-none-any.whl" + distribution = "example_minimal_library{BUILD_USER}", + python_tag = "py3", + stamp = 1, + version = "0.1.{BUILD_TIMESTAMP}", + deps = [ + "//examples/wheel/lib:module_with_data", + "//examples/wheel/lib:simple_module", + ], +) + +# Use py_package to collect all transitive dependencies of a target, +# selecting just the files within a specific python package. +py_package( + name = "example_pkg", + # Only include these Python packages. + packages = ["examples.wheel"], + deps = [":main"], +) + +py_package( + name = "example_pkg_with_data", + packages = ["examples.wheel"], + deps = [":main_with_gen_data"], +) + +py_wheel( + name = "minimal_with_py_package", + # Package data. We're building "example_minimal_package-0.0.1-py3-none-any.whl" + distribution = "example_minimal_package", + python_tag = "py3", + version = "0.0.1", + deps = [":example_pkg"], +) + +# An example that uses all features provided by py_wheel. +py_wheel( + name = "customized", + author = "Example Author with non-ascii characters: żółw", + author_email = "example@example.com", + classifiers = [ + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Developers", + ], + console_scripts = { + "customized_wheel": "examples.wheel.main:main", + }, + description_file = "README.md", + # Package data. We're building "example_customized-0.0.1-py3-none-any.whl" + distribution = "example_customized", + entry_points = { + "console_scripts": ["another = foo.bar:baz"], + "group2": [ + "second = second.main:s", + "first = first.main:f", + ], + }, + extra_distinfo_files = { + "//examples/wheel:NOTICE": "NOTICE", + # Rename the file when packaging to show we can. + "//examples/wheel:README.md": "README", + }, + homepage = "www.example.com", + license = "Apache 2.0", + project_urls = { + "Bug Tracker": "www.example.com/issues", + "Documentation": "www.example.com/docs", + }, + python_tag = "py3", + # Requirements embedded into the wheel metadata. + requires = ["pytest"], + summary = "A one-line summary of this test package", + version = "0.0.1", + deps = [":example_pkg"], +) + +# An example of how to change the wheel package root directory using 'strip_path_prefixes'. +py_wheel( + name = "custom_package_root", + # Package data. We're building "examples_custom_package_root-0.0.1-py3-none-any.whl" + distribution = "examples_custom_package_root", + entry_points = { + "console_scripts": ["main = foo.bar:baz"], + }, + python_tag = "py3", + strip_path_prefixes = [ + "examples", + ], + version = "0.0.1", + deps = [ + ":example_pkg", + ], +) + +py_wheel( + name = "custom_package_root_multi_prefix", + # Package data. We're building "custom_custom_package_root_multi_prefix-0.0.1-py3-none-any.whl" + distribution = "example_custom_package_root_multi_prefix", + python_tag = "py3", + strip_path_prefixes = [ + "examples/wheel/lib", + "examples/wheel", + ], + version = "0.0.1", + deps = [ + ":example_pkg", + ], +) + +py_wheel( + name = "custom_package_root_multi_prefix_reverse_order", + # Package data. We're building "custom_custom_package_root_multi_prefix_reverse_order-0.0.1-py3-none-any.whl" + distribution = "example_custom_package_root_multi_prefix_reverse_order", + python_tag = "py3", + strip_path_prefixes = [ + "examples/wheel", + "examples/wheel/lib", # this is not effective, because the first prefix takes priority + ], + version = "0.0.1", + deps = [ + ":example_pkg", + ], +) + +py_wheel( + name = "python_requires_in_a_package", + distribution = "example_python_requires_in_a_package", + python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + python_tag = "py3", + version = "0.0.1", + deps = [ + ":example_pkg", + ], +) + +py_wheel( + name = "use_rule_with_dir_in_outs", + distribution = "use_rule_with_dir_in_outs", + python_tag = "py3", + version = "0.0.1", + deps = [ + ":example_pkg_with_data", + ], +) + +gen_python_config_settings() + +py_wheel( + name = "python_abi3_binary_wheel", + abi = "abi3", + distribution = "example_python_abi3_binary_wheel", + # these platform strings must line up with test_python_abi3_binary_wheel() in wheel_test.py + platform = select({ + ":aarch64-apple-darwin": "macosx_11_0_arm64", + ":aarch64-unknown-linux-gnu": "manylinux2014_aarch64", + ":x86_64-apple-darwin": "macosx_11_0_x86_64", # this is typically macosx_10_9_x86_64? + ":x86_64-pc-windows-msvc": "win_amd64", + ":x86_64-unknown-linux-gnu": "manylinux2014_x86_64", + }), + python_requires = ">=3.8", + python_tag = "cp38", + version = "0.0.1", +) + +py_wheel( + name = "filename_escaping", + # Per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode + # runs of non-alphanumeric, non-digit symbols should be replaced with a single underscore. + # Unicode non-ascii letters should *not* be replaced with underscore. + distribution = "file~~name-escaping", + python_tag = "py3", + version = "0.0.1-r7", + deps = [":example_pkg"], +) + +py_test( + name = "wheel_test", + srcs = ["wheel_test.py"], + data = [ + ":custom_package_root", + ":custom_package_root_multi_prefix", + ":custom_package_root_multi_prefix_reverse_order", + ":customized", + ":filename_escaping", + ":minimal_with_py_library", + ":minimal_with_py_library_with_stamp", + ":minimal_with_py_package", + ":python_abi3_binary_wheel", + ":python_requires_in_a_package", + ":use_rule_with_dir_in_outs", + ], +) diff --git a/examples/wheel/NOTICE b/examples/wheel/NOTICE new file mode 100644 index 0000000..700336b --- /dev/null +++ b/examples/wheel/NOTICE @@ -0,0 +1 @@ +This is a test "NOTICE" file to be packaged into distribtion dist-info dir. diff --git a/examples/wheel/README.md b/examples/wheel/README.md new file mode 100644 index 0000000..1426ff4 --- /dev/null +++ b/examples/wheel/README.md @@ -0,0 +1 @@ +This is a sample description of a wheel.
\ No newline at end of file diff --git a/examples/wheel/lib/BUILD.bazel b/examples/wheel/lib/BUILD.bazel new file mode 100644 index 0000000..3b59662 --- /dev/null +++ b/examples/wheel/lib/BUILD.bazel @@ -0,0 +1,36 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//python:defs.bzl", "py_library") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +py_library( + name = "simple_module", + srcs = ["simple_module.py"], +) + +py_library( + name = "module_with_data", + srcs = ["module_with_data.py"], + data = [":data.txt"], +) + +genrule( + name = "make_data", + outs = ["data.txt"], + cmd = "echo foo bar baz > $@", +) diff --git a/examples/wheel/lib/module_with_data.py b/examples/wheel/lib/module_with_data.py new file mode 100644 index 0000000..6b661eb --- /dev/null +++ b/examples/wheel/lib/module_with_data.py @@ -0,0 +1,17 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def function(): + return "foo" diff --git a/examples/wheel/lib/simple_module.py b/examples/wheel/lib/simple_module.py new file mode 100644 index 0000000..b69ae2b --- /dev/null +++ b/examples/wheel/lib/simple_module.py @@ -0,0 +1,17 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def function(): + return "bar" diff --git a/examples/wheel/main.py b/examples/wheel/main.py new file mode 100644 index 0000000..7c4d323 --- /dev/null +++ b/examples/wheel/main.py @@ -0,0 +1,30 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import examples.wheel.lib.module_with_data as module_with_data +import examples.wheel.lib.simple_module as simple_module + + +def function(): + return "baz" + + +def main(): + print(function()) + print(module_with_data.function()) + print(simple_module.function()) + + +if __name__ == "__main__": + main() diff --git a/examples/wheel/private/BUILD.bazel b/examples/wheel/private/BUILD.bazel new file mode 100644 index 0000000..3462d35 --- /dev/null +++ b/examples/wheel/private/BUILD.bazel @@ -0,0 +1,7 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +py_binary( + name = "directory_writer", + srcs = ["directory_writer.py"], + visibility = ["//:__subpackages__"], +) diff --git a/examples/wheel/private/directory_writer.py b/examples/wheel/private/directory_writer.py new file mode 100644 index 0000000..7d9a93e --- /dev/null +++ b/examples/wheel/private/directory_writer.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The action executable of the `@rules_python//examples/wheel/private:wheel_utils.bzl%directory_writer` rule.""" + +import argparse +import json +from pathlib import Path +from typing import Tuple + + +def _file_input(value) -> Tuple[Path, str]: + path, content = value.split("=", maxsplit=1) + return (Path(path), json.loads(content)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + + parser.add_argument( + "--output", type=Path, required=True, help="The output directory to create." + ) + parser.add_argument( + "--file", + dest="files", + type=_file_input, + action="append", + help="Files to create within the `output` directory.", + ) + + return parser.parse_args() + + +def main() -> None: + args = parse_args() + + args.output.mkdir(parents=True, exist_ok=True) + + for (path, content) in args.files: + new_file = args.output / path + new_file.parent.mkdir(parents=True, exist_ok=True) + new_file.write_text(content) + + +if __name__ == "__main__": + main() diff --git a/examples/wheel/private/wheel_utils.bzl b/examples/wheel/private/wheel_utils.bzl new file mode 100644 index 0000000..037fed0 --- /dev/null +++ b/examples/wheel/private/wheel_utils.bzl @@ -0,0 +1,73 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper rules for demonstrating `py_wheel` examples""" + +def _directory_writer_impl(ctx): + output = ctx.actions.declare_directory(ctx.attr.out) + + args = ctx.actions.args() + args.add("--output", output.path) + + for path, content in ctx.attr.files.items(): + args.add("--file={}={}".format( + path, + json.encode(content), + )) + + ctx.actions.run( + outputs = [output], + arguments = [args], + executable = ctx.executable._writer, + ) + + return [DefaultInfo( + files = depset([output]), + runfiles = ctx.runfiles(files = [output]), + )] + +directory_writer = rule( + implementation = _directory_writer_impl, + doc = "A rule for generating a directory with the requested content.", + attrs = { + "files": attr.string_dict( + doc = "A mapping of file name to content to create relative to the generated `out` directory.", + ), + "out": attr.string( + doc = "The name of the directory to create", + ), + "_writer": attr.label( + executable = True, + cfg = "exec", + default = Label("//examples/wheel/private:directory_writer"), + ), + }, +) + +def _make_variable_tags_impl(ctx): # buildifier: disable=unused-variable + # This example is contrived. In a real usage, this rule would + # look at flags or dependencies to determine what values to use. + # If all you're doing is setting constant values, then you can simply + # set them in the py_wheel() call. + vars = {} + vars["ABI"] = "cp38" + vars["PYTHON_TAG"] = "cp38" + vars["VERSION"] = "0.99.0" + return [platform_common.TemplateVariableInfo(vars)] + +make_variable_tags = rule( + attrs = {}, + doc = """Make variable tags to pass to a py_wheel rule.""", + implementation = _make_variable_tags_impl, +) diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py new file mode 100644 index 0000000..f51a0ec --- /dev/null +++ b/examples/wheel/wheel_test.py @@ -0,0 +1,414 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import platform +import subprocess +import unittest +import zipfile + + +class WheelTest(unittest.TestCase): + maxDiff = None + + def test_py_library_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_minimal_library-0.0.1-py3-none-any.whl", + ) + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "examples/wheel/lib/module_with_data.py", + "examples/wheel/lib/simple_module.py", + "example_minimal_library-0.0.1.dist-info/WHEEL", + "example_minimal_library-0.0.1.dist-info/METADATA", + "example_minimal_library-0.0.1.dist-info/RECORD", + ], + ) + + def test_py_package_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_minimal_package-0.0.1-py3-none-any.whl", + ) + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "examples/wheel/lib/data.txt", + "examples/wheel/lib/module_with_data.py", + "examples/wheel/lib/simple_module.py", + "examples/wheel/main.py", + "example_minimal_package-0.0.1.dist-info/WHEEL", + "example_minimal_package-0.0.1.dist-info/METADATA", + "example_minimal_package-0.0.1.dist-info/RECORD", + ], + ) + + def test_customized_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_customized-0.0.1-py3-none-any.whl", + ) + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "examples/wheel/lib/data.txt", + "examples/wheel/lib/module_with_data.py", + "examples/wheel/lib/simple_module.py", + "examples/wheel/main.py", + "example_customized-0.0.1.dist-info/WHEEL", + "example_customized-0.0.1.dist-info/METADATA", + "example_customized-0.0.1.dist-info/entry_points.txt", + "example_customized-0.0.1.dist-info/NOTICE", + "example_customized-0.0.1.dist-info/README", + "example_customized-0.0.1.dist-info/RECORD", + ], + ) + record_contents = zf.read("example_customized-0.0.1.dist-info/RECORD") + wheel_contents = zf.read("example_customized-0.0.1.dist-info/WHEEL") + metadata_contents = zf.read("example_customized-0.0.1.dist-info/METADATA") + entry_point_contents = zf.read( + "example_customized-0.0.1.dist-info/entry_points.txt" + ) + + self.assertEqual( + record_contents, + # The entries are guaranteed to be sorted. + b"""\ +example_customized-0.0.1.dist-info/METADATA,sha256=QYQcDJFQSIqan8eiXqL67bqsUfgEAwf2hoK_Lgi1S-0,559 +example_customized-0.0.1.dist-info/NOTICE,sha256=Xpdw-FXET1IRgZ_wTkx1YQfo1-alET0FVf6V1LXO4js,76 +example_customized-0.0.1.dist-info/README,sha256=WmOFwZ3Jga1bHG3JiGRsUheb4UbLffUxyTdHczS27-o,40 +example_customized-0.0.1.dist-info/RECORD,, +example_customized-0.0.1.dist-info/WHEEL,sha256=sobxWSyDDkdg_rinUth-jxhXHqoNqlmNMJY3aTZn2Us,91 +example_customized-0.0.1.dist-info/entry_points.txt,sha256=pqzpbQ8MMorrJ3Jp0ntmpZcuvfByyqzMXXi2UujuXD0,137 +examples/wheel/lib/data.txt,sha256=9vJKEdfLu8bZRArKLroPZJh1XKkK3qFMXiM79MBL2Sg,12 +examples/wheel/lib/module_with_data.py,sha256=8s0Khhcqz3yVsBKv2IB5u4l4TMKh7-c_V6p65WVHPms,637 +examples/wheel/lib/simple_module.py,sha256=z2hwciab_XPNIBNH8B1Q5fYgnJvQTeYf0ZQJpY8yLLY,637 +examples/wheel/main.py,sha256=sgg5iWN_9inYBjm6_Zw27hYdmo-l24fA-2rfphT-IlY,909 +""", + ) + self.assertEqual( + wheel_contents, + b"""\ +Wheel-Version: 1.0 +Generator: bazel-wheelmaker 1.0 +Root-Is-Purelib: true +Tag: py3-none-any +""", + ) + self.assertEqual( + metadata_contents, + b"""\ +Metadata-Version: 2.1 +Name: example_customized +Author: Example Author with non-ascii characters: \xc5\xbc\xc3\xb3\xc5\x82w +Author-email: example@example.com +Home-page: www.example.com +License: Apache 2.0 +Description-Content-Type: text/markdown +Summary: A one-line summary of this test package +Project-URL: Bug Tracker, www.example.com/issues +Project-URL: Documentation, www.example.com/docs +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Requires-Dist: pytest +Version: 0.0.1 + +This is a sample description of a wheel. +""", + ) + self.assertEqual( + entry_point_contents, + b"""\ +[console_scripts] +another = foo.bar:baz +customized_wheel = examples.wheel.main:main + +[group2] +first = first.main:f +second = second.main:s""", + ) + + def test_filename_escaping(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "file_name_escaping-0.0.1_r7-py3-none-any.whl", + ) + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "examples/wheel/lib/data.txt", + "examples/wheel/lib/module_with_data.py", + "examples/wheel/lib/simple_module.py", + "examples/wheel/main.py", + # PEP calls for replacing only in the archive filename. + # Alas setuptools also escapes in the dist-info directory + # name, so let's be compatible. + "file_name_escaping-0.0.1_r7.dist-info/WHEEL", + "file_name_escaping-0.0.1_r7.dist-info/METADATA", + "file_name_escaping-0.0.1_r7.dist-info/RECORD", + ], + ) + metadata_contents = zf.read( + "file_name_escaping-0.0.1_r7.dist-info/METADATA" + ) + self.assertEqual( + metadata_contents, + b"""\ +Metadata-Version: 2.1 +Name: file~~name-escaping +Version: 0.0.1-r7 + +UNKNOWN +""", + ) + + def test_custom_package_root_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "examples_custom_package_root-0.0.1-py3-none-any.whl", + ) + + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "wheel/lib/data.txt", + "wheel/lib/module_with_data.py", + "wheel/lib/simple_module.py", + "wheel/main.py", + "examples_custom_package_root-0.0.1.dist-info/WHEEL", + "examples_custom_package_root-0.0.1.dist-info/METADATA", + "examples_custom_package_root-0.0.1.dist-info/entry_points.txt", + "examples_custom_package_root-0.0.1.dist-info/RECORD", + ], + ) + + record_contents = zf.read( + "examples_custom_package_root-0.0.1.dist-info/RECORD" + ).decode("utf-8") + + # Ensure RECORD files do not have leading forward slashes + for line in record_contents.splitlines(): + self.assertFalse(line.startswith("/")) + + def test_custom_package_root_multi_prefix_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_custom_package_root_multi_prefix-0.0.1-py3-none-any.whl", + ) + + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "data.txt", + "module_with_data.py", + "simple_module.py", + "main.py", + "example_custom_package_root_multi_prefix-0.0.1.dist-info/WHEEL", + "example_custom_package_root_multi_prefix-0.0.1.dist-info/METADATA", + "example_custom_package_root_multi_prefix-0.0.1.dist-info/RECORD", + ], + ) + + record_contents = zf.read( + "example_custom_package_root_multi_prefix-0.0.1.dist-info/RECORD" + ).decode("utf-8") + + # Ensure RECORD files do not have leading forward slashes + for line in record_contents.splitlines(): + self.assertFalse(line.startswith("/")) + + def test_custom_package_root_multi_prefix_reverse_order_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_custom_package_root_multi_prefix_reverse_order-0.0.1-py3-none-any.whl", + ) + + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "lib/data.txt", + "lib/module_with_data.py", + "lib/simple_module.py", + "main.py", + "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/WHEEL", + "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/METADATA", + "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/RECORD", + ], + ) + + record_contents = zf.read( + "example_custom_package_root_multi_prefix_reverse_order-0.0.1.dist-info/RECORD" + ).decode("utf-8") + + # Ensure RECORD files do not have leading forward slashes + for line in record_contents.splitlines(): + self.assertFalse(line.startswith("/")) + + def test_python_requires_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_python_requires_in_a_package-0.0.1-py3-none-any.whl", + ) + with zipfile.ZipFile(filename) as zf: + metadata_contents = zf.read( + "example_python_requires_in_a_package-0.0.1.dist-info/METADATA" + ) + # The entries are guaranteed to be sorted. + self.assertEqual( + metadata_contents, + b"""\ +Metadata-Version: 2.1 +Name: example_python_requires_in_a_package +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +Version: 0.0.1 + +UNKNOWN +""", + ) + + def test_python_abi3_binary_wheel(self): + arch = "amd64" + if platform.system() != "Windows": + arch = subprocess.check_output(["uname", "-m"]).strip().decode() + # These strings match the strings from py_wheel() in BUILD + os_strings = { + "Linux": "manylinux2014", + "Darwin": "macosx_11_0", + "Windows": "win", + } + os_string = os_strings[platform.system()] + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + f"example_python_abi3_binary_wheel-0.0.1-cp38-abi3-{os_string}_{arch}.whl", + ) + with zipfile.ZipFile(filename) as zf: + metadata_contents = zf.read( + "example_python_abi3_binary_wheel-0.0.1.dist-info/METADATA" + ) + # The entries are guaranteed to be sorted. + self.assertEqual( + metadata_contents, + b"""\ +Metadata-Version: 2.1 +Name: example_python_abi3_binary_wheel +Requires-Python: >=3.8 +Version: 0.0.1 + +UNKNOWN +""", + ) + wheel_contents = zf.read( + "example_python_abi3_binary_wheel-0.0.1.dist-info/WHEEL" + ) + self.assertEqual( + wheel_contents.decode(), + f"""\ +Wheel-Version: 1.0 +Generator: bazel-wheelmaker 1.0 +Root-Is-Purelib: false +Tag: cp38-abi3-{os_string}_{arch} +""", + ) + + def test_rule_creates_directory_and_is_included_in_wheel(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "use_rule_with_dir_in_outs-0.0.1-py3-none-any.whl", + ) + + with zipfile.ZipFile(filename) as zf: + self.assertEqual( + zf.namelist(), + [ + "examples/wheel/main.py", + "examples/wheel/someDir/foo.py", + "use_rule_with_dir_in_outs-0.0.1.dist-info/WHEEL", + "use_rule_with_dir_in_outs-0.0.1.dist-info/METADATA", + "use_rule_with_dir_in_outs-0.0.1.dist-info/RECORD", + ], + ) + + def test_rule_expands_workspace_status_keys_in_wheel_metadata(self): + filename = os.path.join( + os.environ["TEST_SRCDIR"], + "rules_python", + "examples", + "wheel", + "example_minimal_library_BUILD_USER_-0.1._BUILD_TIMESTAMP_-py3-none-any.whl", + ) + + with zipfile.ZipFile(filename) as zf: + metadata_file = None + for f in zf.namelist(): + self.assertNotIn("_BUILD_TIMESTAMP_", f) + self.assertNotIn("_BUILD_USER_", f) + if os.path.basename(f) == "METADATA": + metadata_file = f + self.assertIsNotNone(metadata_file) + + version = None + name = None + with zf.open(metadata_file) as fp: + for line in fp: + if line.startswith(b"Version:"): + version = line.decode().split()[-1] + if line.startswith(b"Name:"): + name = line.decode().split()[-1] + self.assertIsNotNone(version) + self.assertIsNotNone(name) + self.assertNotIn("{BUILD_TIMESTAMP}", version) + self.assertNotIn("{BUILD_USER}", name) + + +if __name__ == "__main__": + unittest.main() |